From caa82d5770127aad85bdc93aa571aa2acd5fdfd0 Mon Sep 17 00:00:00 2001 From: Sarat Poluri Date: Wed, 27 May 2026 22:19:43 -0700 Subject: [PATCH 01/17] ITEP-92735: Mapping service should communicate with outside world over proxy --- .../microservices/mapping-service/api-docs/mapping-api.yaml | 6 +++--- .../microservices/mapping-service/mapping-service.md | 2 +- manager/config/default-ssl.conf | 3 +++ mapping/tests/requirements-test.txt | 4 ++++ sample_data/docker-compose-dl-streamer-example.yml | 4 ++-- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/user-guide/microservices/mapping-service/api-docs/mapping-api.yaml b/docs/user-guide/microservices/mapping-service/api-docs/mapping-api.yaml index 820a47bc2f..99c3fe83e0 100644 --- a/docs/user-guide/microservices/mapping-service/api-docs/mapping-api.yaml +++ b/docs/user-guide/microservices/mapping-service/api-docs/mapping-api.yaml @@ -19,9 +19,9 @@ info: servers: - url: https://localhost:8444 - description: Local development server - - url: https://0.0.0.0:8444 - description: Docker container server + description: Direct standalone access + - url: https://localhost/api/mapping + description: Access via Apache reverse proxy when web service is running tags: - name: reconstruction diff --git a/docs/user-guide/microservices/mapping-service/mapping-service.md b/docs/user-guide/microservices/mapping-service/mapping-service.md index 4fc8811a9a..524585fc05 100644 --- a/docs/user-guide/microservices/mapping-service/mapping-service.md +++ b/docs/user-guide/microservices/mapping-service/mapping-service.md @@ -145,7 +145,7 @@ payload = { "output_format": "glb" } -# Send request +# Send request directly to mapping service response = requests.post("https://localhost:8444/reconstruction", json=payload) result = response.json() diff --git a/manager/config/default-ssl.conf b/manager/config/default-ssl.conf index 66beecbb9e..e8128fefba 100644 --- a/manager/config/default-ssl.conf +++ b/manager/config/default-ssl.conf @@ -137,6 +137,9 @@ SSLProxyEngine on + ProxyPass /api/mapping/ https://mapping.scenescape.intel.com:8444/ + ProxyPassReverse /api/mapping/ https://mapping.scenescape.intel.com:8444/ + ProxyPass /v1/ https://autocalibration.scenescape.intel.com:8443/v1/ ProxyPassReverse /v1/ https://autocalibration.scenescape.intel.com:8443/v1/ diff --git a/mapping/tests/requirements-test.txt b/mapping/tests/requirements-test.txt index a36041fd47..ba349064ad 100644 --- a/mapping/tests/requirements-test.txt +++ b/mapping/tests/requirements-test.txt @@ -17,3 +17,7 @@ pytest-timeout==2.1.0 # For test timeouts # Required for mocking responses==0.23.1 + +# Service runtime dependencies needed to import mapping modules under test +flask==3.1.3 +flask-cors==6.0.0 diff --git a/sample_data/docker-compose-dl-streamer-example.yml b/sample_data/docker-compose-dl-streamer-example.yml index 5d69889f73..b3c614a54d 100644 --- a/sample_data/docker-compose-dl-streamer-example.yml +++ b/sample_data/docker-compose-dl-streamer-example.yml @@ -543,8 +543,8 @@ services: scenescape: aliases: - mapping.scenescape.intel.com - ports: - - "8444:8444" + # ports: + # - "8444:8444" volumes: - vol-mapping-model-weights:/workspace/model_weights - vol-mapping-torch-cache:/workspace/.cache/torch From 27b1237a0f7ae5e9103c3e0524b6644c2ca69e91 Mon Sep 17 00:00:00 2001 From: Sarat Poluri Date: Wed, 27 May 2026 23:05:53 -0700 Subject: [PATCH 02/17] Uniform access url paths --- .../mapping-service/api-docs/mapping-api.yaml | 2 +- manager/config/default-ssl.conf | 12 +++++----- manager/config/webserver-init | 10 ++++---- manager/src/manager/static/js/calibration.js | 24 +++++++++++-------- manager/src/manager/static/js/sscape.js | 2 +- .../manager/static/js/thing/scenecamera.js | 2 +- tests/api/conftest.py | 4 ++-- 7 files changed, 31 insertions(+), 25 deletions(-) diff --git a/docs/user-guide/microservices/mapping-service/api-docs/mapping-api.yaml b/docs/user-guide/microservices/mapping-service/api-docs/mapping-api.yaml index 99c3fe83e0..0893f7bf30 100644 --- a/docs/user-guide/microservices/mapping-service/api-docs/mapping-api.yaml +++ b/docs/user-guide/microservices/mapping-service/api-docs/mapping-api.yaml @@ -20,7 +20,7 @@ info: servers: - url: https://localhost:8444 description: Direct standalone access - - url: https://localhost/api/mapping + - url: https://localhost/api/v1/mapping description: Access via Apache reverse proxy when web service is running tags: diff --git a/manager/config/default-ssl.conf b/manager/config/default-ssl.conf index e8128fefba..65e88078ba 100644 --- a/manager/config/default-ssl.conf +++ b/manager/config/default-ssl.conf @@ -137,17 +137,17 @@ SSLProxyEngine on - ProxyPass /api/mapping/ https://mapping.scenescape.intel.com:8444/ - ProxyPassReverse /api/mapping/ https://mapping.scenescape.intel.com:8444/ + ProxyPass /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/ + ProxyPassReverse /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/ - ProxyPass /v1/ https://autocalibration.scenescape.intel.com:8443/v1/ - ProxyPassReverse /v1/ https://autocalibration.scenescape.intel.com:8443/v1/ + ProxyPass /api/v1/autocalibration/ https://autocalibration.scenescape.intel.com:8443/v1/ + ProxyPassReverse /api/v1/autocalibration/ https://autocalibration.scenescape.intel.com:8443/v1/ ProxyPass /mqtt wss://broker.scenescape.intel.com:1884/ timeout=10 ProxyPassReverse /mqtt wss://broker.scenescape.intel.com:1884/ - ProxyPass "/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/socket.io/" - ProxyPassReverse "/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/socket.io/" + ProxyPass "/api/v1/autocalibration/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/socket.io/" + ProxyPassReverse "/api/v1/autocalibration/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/socket.io/" WSGIPassAuthorization On diff --git a/manager/config/webserver-init b/manager/config/webserver-init index a5e0bc4b4a..91ec909f5c 100755 --- a/manager/config/webserver-init +++ b/manager/config/webserver-init @@ -67,12 +67,14 @@ fi if [ -n "${KUBERNETES_SERVICE_HOST}" ] ; then sed -i '/<\/VirtualHost>/i \ SSLProxyEngine on \ + ProxyPass /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/ \ + ProxyPassReverse /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/ \ ProxyPass /mqtt wss://broker.scenescape.intel.com:1884/ \ ProxyPassReverse /mqtt wss://broker.scenescape.intel.com:1884/ \ - ProxyPass /v1/ https://autocalibration.scenescape.intel.com:8443/v1/ \ - ProxyPassReverse /v1/ https://autocalibration.scenescape.intel.com:8443/v1/ \ - ProxyPass "/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/socket.io/" \ - ProxyPassReverse "/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/socket.io/" \ + ProxyPass /api/v1/autocalibration/ https://autocalibration.scenescape.intel.com:8443/v1/ \ + ProxyPassReverse /api/v1/autocalibration/ https://autocalibration.scenescape.intel.com:8443/v1/ \ + ProxyPass "/api/v1/autocalibration/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/socket.io/" \ + ProxyPassReverse "/api/v1/autocalibration/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/socket.io/" \ \n WSGIPassAuthorization On' /etc/apache2/sites-available/000-default.conf fi diff --git a/manager/src/manager/static/js/calibration.js b/manager/src/manager/static/js/calibration.js index 482e8c5b7b..cc25d0f5bf 100644 --- a/manager/src/manager/static/js/calibration.js +++ b/manager/src/manager/static/js/calibration.js @@ -8,6 +8,7 @@ import { ConvergedCameraCalibration } from "/static/js/cameracalibrate.js"; var calibration_strategy; let camera_calibration; +const AUTOCALIB_PROXY_BASE = "/api/v1/autocalibration"; // Initialize after DOM is ready document.addEventListener("DOMContentLoaded", function () { @@ -17,14 +18,17 @@ document.addEventListener("DOMContentLoaded", function () { async function startCameraCalibration(cameraUID, image, intrinsics) { try { - const response = await fetch(`/v1/cameras/${cameraUID}/calibration`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - image: image, - intrinsics: intrinsics, - }), - }); + const response = await fetch( + `${AUTOCALIB_PROXY_BASE}/cameras/${cameraUID}/calibration`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + image: image, + intrinsics: intrinsics, + }), + }, + ); if (!response.ok) { throw new Error(`HTTP ${response.status} - ${response.statusText}`); @@ -41,7 +45,7 @@ async function startCameraCalibration(cameraUID, image, intrinsics) { async function getCalibrationServiceStatus() { try { - const response = await fetch("/v1/status", { + const response = await fetch(`${AUTOCALIB_PROXY_BASE}/status`, { method: "GET", headers: { "Content-Type": "application/json", @@ -60,7 +64,7 @@ async function getCalibrationServiceStatus() { } async function registerScene(sceneId) { - const url = `/v1/scenes/${sceneId}/registration`; + const url = `${AUTOCALIB_PROXY_BASE}/scenes/${sceneId}/registration`; try { const response = await fetch(url, { diff --git a/manager/src/manager/static/js/sscape.js b/manager/src/manager/static/js/sscape.js index 2f9fbfd951..01b8aa6cb5 100644 --- a/manager/src/manager/static/js/sscape.js +++ b/manager/src/manager/static/js/sscape.js @@ -51,7 +51,7 @@ points = maps = rois = tripwires = []; dragging = drawing = adding = editing = fullscreen = false; const socket = io({ - path: "/socket.io", + path: "/api/v1/autocalibration/socket.io", transports: ["websocket"], }); diff --git a/manager/src/manager/static/js/thing/scenecamera.js b/manager/src/manager/static/js/thing/scenecamera.js index b4cb613c16..dc3535f066 100644 --- a/manager/src/manager/static/js/thing/scenecamera.js +++ b/manager/src/manager/static/js/thing/scenecamera.js @@ -114,7 +114,7 @@ export default class SceneCamera extends THREE.Object3D { this.cameraCapture = null; this.currentFrame = null; this.socket = io({ - path: "/socket.io", + path: "/api/v1/autocalibration/socket.io", transports: ["websocket"], }); diff --git a/tests/api/conftest.py b/tests/api/conftest.py index dde5074456..0868e69522 100644 --- a/tests/api/conftest.py +++ b/tests/api/conftest.py @@ -173,11 +173,11 @@ def http_client(token, base_url) -> RESTClient: @pytest.fixture(scope='session') def autocalib_client(token, base_url) -> RESTClient: - return RESTClient(url=f"{base_url}/v1", token=token, verify_ssl=False) + return RESTClient(url=f"{base_url}/api/v1/autocalibration", token=token, verify_ssl=False) @pytest.fixture(scope='session') def mapping_client(token, base_url) -> MappingClient: - return MappingClient(url=f"{base_url}:8444", token=token, verify_ssl=False) + return MappingClient(url=f"{base_url}/api/v1/mapping/", token=token, verify_ssl=False) @pytest.fixture(scope='session') def api_map(http_client, autocalib_client, mapping_client): From 4460fad1aaf0468af89f86f9652f4d5a6ab03cb4 Mon Sep 17 00:00:00 2001 From: Sarat Poluri Date: Wed, 27 May 2026 23:09:09 -0700 Subject: [PATCH 03/17] Fix test ports and autocalibration test URLs to use Apache proxy - Comment out direct host port exposure (8443, 8444) in test compose files - Update BASE_URL in autocalibration functional tests to route through the Apache proxy at web.scenescape.intel.com instead of directly to autocalibration.scenescape.intel.com:8443 - Update all /v1/ URL paths to /api/v1/autocalibration/ to match the new ProxyPass configuration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/compose/autocalibration.yml | 4 ++-- tests/compose/mapping.yml | 4 ++-- tests/functional/test_apriltag_registration_update.py | 7 ++++--- tests/functional/test_auto_calibration.py | 11 ++++++----- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/compose/autocalibration.yml b/tests/compose/autocalibration.yml index cef50558f8..293d37405d 100644 --- a/tests/compose/autocalibration.yml +++ b/tests/compose/autocalibration.yml @@ -48,8 +48,8 @@ services: --restport 8443 --ssl-certfile /run/secrets/certs/scenescape-autocalibration.crt --ssl-keyfile /run/secrets/certs/scenescape-autocalibration.key - ports: - - "8443:8443" + # ports: + # - "8443:8443" environment: - EGL_PLATFORM=surfaceless - NETVLAD_MODEL_DIR=/usr/local/lib/python3.11/site-packages/third_party/netvlad diff --git a/tests/compose/mapping.yml b/tests/compose/mapping.yml index 36e16a3b9f..dd935ac254 100644 --- a/tests/compose/mapping.yml +++ b/tests/compose/mapping.yml @@ -25,8 +25,8 @@ services: scenescape-test: aliases: - mapping.scenescape.intel.com - ports: - - "8444:8444" + # ports: + # - "8444:8444" volumes: - vol-mapping-model-weights:/workspace/model_weights - vol-mapping-torch-cache:/workspace/.cache/torch diff --git a/tests/functional/test_apriltag_registration_update.py b/tests/functional/test_apriltag_registration_update.py index bee73b77a7..3f3d8c76c9 100644 --- a/tests/functional/test_apriltag_registration_update.py +++ b/tests/functional/test_apriltag_registration_update.py @@ -19,7 +19,8 @@ POLL_INTERVAL = 5 POLL_TIMEOUT = 60 -BASE_URL = "https://autocalibration.scenescape.intel.com:8443" +BASE_URL = "https://web.scenescape.intel.com" +AUTOCALIB_BASE = f"{BASE_URL}/api/v1/autocalibration" MAP_APRILTAG_COUNT = 7 # number of apriltags present in Queuing scene @@ -37,7 +38,7 @@ def __init__(self, testName, request, recordXMLAttribute): res = self.rest.authenticate(self.params['user'], self.params['password']) assert res, res.errors - r = requests.get(f"{BASE_URL}/v1/status", verify=self.rootcert, timeout=10) + r = requests.get(f"{AUTOCALIB_BASE}/status", verify=self.rootcert, timeout=10) assert r.ok, f"Autocalibration status check failed: {r.status_code} {r.text}" status = r.json() assert status.get('status') == 'running', \ @@ -64,7 +65,7 @@ def _force_scene_unregistered(self): def _trigger_registration(self): """Explicitly POST to the autocalibration service to start scene registration""" - url = f"{BASE_URL}/v1/scenes/{self.scene_id}/registration" + url = f"{AUTOCALIB_BASE}/scenes/{self.scene_id}/registration" r = requests.post(url, json={}, verify=self.rootcert, timeout=10) assert r.status_code in (200, 202), \ f"POST registration returned {r.status_code}: {r.text}" diff --git a/tests/functional/test_auto_calibration.py b/tests/functional/test_auto_calibration.py index b582529d3d..128c70e5c3 100644 --- a/tests/functional/test_auto_calibration.py +++ b/tests/functional/test_auto_calibration.py @@ -31,7 +31,8 @@ ) MAX_WAIT = 5 -BASE_URL = "https://autocalibration.scenescape.intel.com:8443" +BASE_URL = "https://web.scenescape.intel.com" +AUTOCALIB_BASE = f"{BASE_URL}/api/v1/autocalibration" EXPECTED_RESULT_1 = { "calibration_points_2d": [ @@ -164,7 +165,7 @@ def obscure_detected_apriltag(self, image_path, tag_family="tag36h11", return base64.b64encode(buf).decode("utf-8") def get_status(self): - url = f"{BASE_URL}/v1/status" + url = f"{AUTOCALIB_BASE}/status" try: r = requests.get(url, verify=self.rootcert) log.info(f"Service status: {r.json()}") @@ -174,7 +175,7 @@ def get_status(self): return None def register_scene(self, method="POST", poll_interval=5, timeout=60): - url = f"{BASE_URL}/v1/scenes/{self.scene_id}/registration" + url = f"{AUTOCALIB_BASE}/scenes/{self.scene_id}/registration" try: if method.upper() == "POST": r = requests.post(url, json={}, verify=self.rootcert) @@ -208,7 +209,7 @@ def register_scene(self, method="POST", poll_interval=5, timeout=60): return None def start_calibration(self, image_b64, intrinsics=None): - url = f"{BASE_URL}/v1/cameras/{self.camera_id}/calibration" + url = f"{AUTOCALIB_BASE}/cameras/{self.camera_id}/calibration" payload = {"image": image_b64} if intrinsics is not None: payload["intrinsics"] = intrinsics @@ -221,7 +222,7 @@ def start_calibration(self, image_b64, intrinsics=None): return None def get_calibration_status(self): - url = f"{BASE_URL}/v1/cameras/{self.camera_id}/calibration" + url = f"{AUTOCALIB_BASE}/cameras/{self.camera_id}/calibration" try: r = requests.get(url, verify=self.rootcert) data = r.json() From 0792cb7fb0b9809410722ecaa9ee441c22a06a67 Mon Sep 17 00:00:00 2001 From: Sarat Poluri Date: Wed, 27 May 2026 23:54:37 -0700 Subject: [PATCH 04/17] Address review comments --- .../mapping-service/mapping-service.md | 41 +++++++++++-------- .../test_apriltag_registration_update.py | 7 ++-- tests/functional/test_auto_calibration.py | 11 +++-- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/docs/user-guide/microservices/mapping-service/mapping-service.md b/docs/user-guide/microservices/mapping-service/mapping-service.md index 524585fc05..3a64a8163d 100644 --- a/docs/user-guide/microservices/mapping-service/mapping-service.md +++ b/docs/user-guide/microservices/mapping-service/mapping-service.md @@ -130,34 +130,39 @@ the service from source and running it. ```python import base64 import requests - -# Encode images to base64 -def encode_image(image_path): - with open(image_path, "rb") as f: - return base64.b64encode(f.read()).decode('utf-8') - -# Prepare request -payload = { - "images": [ - {"data": encode_image("image1.jpg"), "filename": "image1.jpg"}, - {"data": encode_image("image2.jpg"), "filename": "image2.jpg"} - ], - "output_format": "glb" +from pathlib import Path + +# Prepare multipart request +files = [] +handles = [] +for image_path in ["image1.jpg", "image2.jpg"]: + path = Path(image_path) + handle = path.open("rb") + handles.append(handle) + files.append(("images", (path.name, handle, "image/jpeg"))) + +data = { + "output_format": "glb", + "mesh_type": "mesh", } -# Send request directly to mapping service -response = requests.post("https://localhost:8444/reconstruction", json=payload) -result = response.json() +try: + # Send request through the Apache reverse proxy used in the full stack deployment + response = requests.post("https://localhost/api/v1/mapping/reconstruction", data=data, files=files) + result = response.json() -if result["success"]: + if result["success"]: # Save GLB file glb_data = base64.b64decode(result["glb_data"]) with open("output.glb", "wb") as f: - f.write(glb_data) + f.write(glb_data) print(f"Model used: {result['model']}") print(f"Processing time: {result['processing_time']:.2f}s") print(f"Camera poses: {len(result['camera_poses'])}") +finally: + for handle in handles: + handle.close() ``` ### Using the Included Client diff --git a/tests/functional/test_apriltag_registration_update.py b/tests/functional/test_apriltag_registration_update.py index 3f3d8c76c9..049f242383 100644 --- a/tests/functional/test_apriltag_registration_update.py +++ b/tests/functional/test_apriltag_registration_update.py @@ -19,8 +19,6 @@ POLL_INTERVAL = 5 POLL_TIMEOUT = 60 -BASE_URL = "https://web.scenescape.intel.com" -AUTOCALIB_BASE = f"{BASE_URL}/api/v1/autocalibration" MAP_APRILTAG_COUNT = 7 # number of apriltags present in Queuing scene @@ -32,13 +30,14 @@ def __init__(self, testName, request, recordXMLAttribute): super().__init__(testName, request, recordXMLAttribute) self.scene_id = '302cf49a-97ec-402d-a324-c5077b280b7b' self.original_apriltag_size = None + self.autocalib_base = f"{self.params['weburl']}/api/v1/autocalibration" self.rootcert = self.params['rootcert'] self.rest = RESTClient(self.params['resturl'], rootcert=self.params['rootcert']) res = self.rest.authenticate(self.params['user'], self.params['password']) assert res, res.errors - r = requests.get(f"{AUTOCALIB_BASE}/status", verify=self.rootcert, timeout=10) + r = requests.get(f"{self.autocalib_base}/status", verify=self.rootcert, timeout=10) assert r.ok, f"Autocalibration status check failed: {r.status_code} {r.text}" status = r.json() assert status.get('status') == 'running', \ @@ -65,7 +64,7 @@ def _force_scene_unregistered(self): def _trigger_registration(self): """Explicitly POST to the autocalibration service to start scene registration""" - url = f"{AUTOCALIB_BASE}/scenes/{self.scene_id}/registration" + url = f"{self.autocalib_base}/scenes/{self.scene_id}/registration" r = requests.post(url, json={}, verify=self.rootcert, timeout=10) assert r.status_code in (200, 202), \ f"POST registration returned {r.status_code}: {r.text}" diff --git a/tests/functional/test_auto_calibration.py b/tests/functional/test_auto_calibration.py index 128c70e5c3..7b8711e732 100644 --- a/tests/functional/test_auto_calibration.py +++ b/tests/functional/test_auto_calibration.py @@ -31,8 +31,6 @@ ) MAX_WAIT = 5 -BASE_URL = "https://web.scenescape.intel.com" -AUTOCALIB_BASE = f"{BASE_URL}/api/v1/autocalibration" EXPECTED_RESULT_1 = { "calibration_points_2d": [ @@ -109,6 +107,7 @@ def __init__(self, testName, request, recordXMLAttribute, self.intrinsics = intrinsics self.expectedResult = expectedResult self.rootcert = self.params['rootcert'] + self.autocalib_base = f"{self.params['weburl']}/api/v1/autocalibration" self.rest = RESTClient(self.params['resturl'], rootcert=self.params['rootcert']) res = self.rest.authenticate(self.params['user'], self.params['password']) @@ -165,7 +164,7 @@ def obscure_detected_apriltag(self, image_path, tag_family="tag36h11", return base64.b64encode(buf).decode("utf-8") def get_status(self): - url = f"{AUTOCALIB_BASE}/status" + url = f"{self.autocalib_base}/status" try: r = requests.get(url, verify=self.rootcert) log.info(f"Service status: {r.json()}") @@ -175,7 +174,7 @@ def get_status(self): return None def register_scene(self, method="POST", poll_interval=5, timeout=60): - url = f"{AUTOCALIB_BASE}/scenes/{self.scene_id}/registration" + url = f"{self.autocalib_base}/scenes/{self.scene_id}/registration" try: if method.upper() == "POST": r = requests.post(url, json={}, verify=self.rootcert) @@ -209,7 +208,7 @@ def register_scene(self, method="POST", poll_interval=5, timeout=60): return None def start_calibration(self, image_b64, intrinsics=None): - url = f"{AUTOCALIB_BASE}/cameras/{self.camera_id}/calibration" + url = f"{self.autocalib_base}/cameras/{self.camera_id}/calibration" payload = {"image": image_b64} if intrinsics is not None: payload["intrinsics"] = intrinsics @@ -222,7 +221,7 @@ def start_calibration(self, image_b64, intrinsics=None): return None def get_calibration_status(self): - url = f"{AUTOCALIB_BASE}/cameras/{self.camera_id}/calibration" + url = f"{self.autocalib_base}/cameras/{self.camera_id}/calibration" try: r = requests.get(url, verify=self.rootcert) data = r.json() From 32ae9781225f9668b66511e0f1665acbe129247e Mon Sep 17 00:00:00 2001 From: Sarat Poluri Date: Thu, 28 May 2026 00:17:03 -0700 Subject: [PATCH 05/17] Update endpoints in Agents.md --- manager/Agents.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/manager/Agents.md b/manager/Agents.md index 673f34c1a6..b466a121c8 100644 --- a/manager/Agents.md +++ b/manager/Agents.md @@ -69,7 +69,8 @@ The **Manager** service is the Django-based web UI and REST API gateway for Inte - `/api/v1/scenes/`: Scene management (CRUD) - `/api/v1/cameras/`: Camera configuration -- `/api/v1/calibration/`: Trigger calibration +- `/api/v1/calculateintrinsics/`: Calculate camera intrinsics +- `/api/v1/aclcheck/`: Validate broker topic ACLs - `/api/v1/objects/`: Query tracked objects - `/api/v1/health/`: Health check @@ -276,8 +277,8 @@ docker compose exec manager python manage.py showmigrations ### Auto Calibration -- Manager UI triggers calibration requests -- Displays calibration status from Auto Calibration service +- Manager UI triggers calibration requests through the web-proxied Auto Calibration service +- Autocalibration endpoints live under `/api/v1/autocalibration/` when accessed through the web container - Stores completed calibration parameters in database ### PostgreSQL From 5a0b7ab38f13711cd0d8fe0987ee5b4e4e2da86b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 16:11:29 +0000 Subject: [PATCH 06/17] Handle CI chmod failure for secrets directory --- Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3ca4ebc83e..f06267eb87 100644 --- a/Makefile +++ b/Makefile @@ -724,7 +724,13 @@ init-secrets: $(SECRETSDIR) certificates auth-secrets $(SECRETSDIR): mkdir -p $@ - chmod go-rwx $(SECRETSDIR) + @if ! chmod go-rwx $(SECRETSDIR); then \ + if [ "$${CI}" = "true" ]; then \ + echo "Warning: could not set restrictive permissions on $(SECRETSDIR) in CI; continuing."; \ + else \ + exit 1; \ + fi; \ + fi .PHONY: $(SECRETSDIR) certificates certificates: From d3c499a99db66cde6d8be09201ad59f64f08da7e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 16:13:43 +0000 Subject: [PATCH 07/17] Clarify CI chmod warning security implication --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f06267eb87..e581fd68b0 100644 --- a/Makefile +++ b/Makefile @@ -726,7 +726,7 @@ $(SECRETSDIR): mkdir -p $@ @if ! chmod go-rwx $(SECRETSDIR); then \ if [ "$${CI}" = "true" ]; then \ - echo "Warning: could not set restrictive permissions on $(SECRETSDIR) in CI; continuing."; \ + echo "Warning: could not set restrictive permissions on $(SECRETSDIR) in CI; secrets may be more exposed on this filesystem."; \ else \ exit 1; \ fi; \ From 5e236afd85a42ed8f12b852ce6ffc3d9bc1ddb14 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 16:14:56 +0000 Subject: [PATCH 08/17] Note CI isolation requirement in chmod warning --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e581fd68b0..f3847c7c06 100644 --- a/Makefile +++ b/Makefile @@ -726,7 +726,7 @@ $(SECRETSDIR): mkdir -p $@ @if ! chmod go-rwx $(SECRETSDIR); then \ if [ "$${CI}" = "true" ]; then \ - echo "Warning: could not set restrictive permissions on $(SECRETSDIR) in CI; secrets may be more exposed on this filesystem."; \ + echo "Warning: could not set restrictive permissions on $(SECRETSDIR) in CI; secrets may be more exposed on this filesystem. Ensure runner isolation controls are in place."; \ else \ exit 1; \ fi; \ From 8175e2818fb4009f6fdd1b37dfbc27285db557d8 Mon Sep 17 00:00:00 2001 From: Sarat Poluri Date: Sun, 31 May 2026 19:20:59 -0700 Subject: [PATCH 09/17] Standardize service based api client code --- autocalibration/src/autocalibration_client.py | 32 +++++ .../_assets/autocalibration-api.yaml | 3 + .../mapping-service/api-docs/mapping-api.yaml | 6 +- .../mapping-service/build-from-source.md | 4 +- .../mapping-service/mapping-service.md | 16 ++- .../templates/mapping/deployment.yaml | 2 +- manager/config/default-ssl.conf | 3 + manager/src/manager/mesh_generator.py | 2 +- mapping/src/api_service_base.py | 5 + mapping/src/mapping_client.py | 115 ++++++++++++++++ mapping/tools/client_example.py | 115 +++++----------- .../docker-compose-dl-streamer-example.yml | 2 +- .../src/scene_common/client_factory.py | 93 +++++++++++++ scene_common/src/scene_common/rest_client.py | 56 -------- tests/api/conftest.py | 29 +++- tests/api/mapping_client.py | 125 ------------------ tests/api/test_sscape_api.py | 16 +-- tests/compose/mapping.yml | 2 +- 18 files changed, 336 insertions(+), 290 deletions(-) create mode 100644 autocalibration/src/autocalibration_client.py create mode 100644 mapping/src/mapping_client.py create mode 100644 scene_common/src/scene_common/client_factory.py delete mode 100644 tests/api/mapping_client.py diff --git a/autocalibration/src/autocalibration_client.py b/autocalibration/src/autocalibration_client.py new file mode 100644 index 0000000000..1fbb594265 --- /dev/null +++ b/autocalibration/src/autocalibration_client.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: (C) 2026 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from scene_common.rest_client import RESTClient + + +class AutoCalibrationClient(RESTClient): + """Client for auto-calibration REST endpoints.""" + + def getStatus(self): + """Gets auto-calibration service status.""" + return self._get("status", None) + + def registerScene(self, sceneId, data): + """Register a scene for auto-calibration.""" + return self._create(f"scenes/{sceneId}/registration", data) + + def getSceneRegistrationStatus(self, sceneId): + """Gets scene registration status.""" + return self._get(f"scenes/{sceneId}/registration", None) + + def updateSceneRegistration(self, sceneId, data): + """Updates scene registration.""" + return self._update(f"scenes/{sceneId}/registration", data) + + def calibrateCamera(self, cameraId, data): + """Calibrate a camera.""" + return self._create(f"cameras/{cameraId}/calibration", data) + + def getCameraCalibrationStatus(self, cameraId): + """Gets camera calibration status.""" + return self._get(f"cameras/{cameraId}/calibration", None) diff --git a/docs/user-guide/microservices/auto-calibration/_assets/autocalibration-api.yaml b/docs/user-guide/microservices/auto-calibration/_assets/autocalibration-api.yaml index b24693829e..69ea119ffd 100644 --- a/docs/user-guide/microservices/auto-calibration/_assets/autocalibration-api.yaml +++ b/docs/user-guide/microservices/auto-calibration/_assets/autocalibration-api.yaml @@ -17,6 +17,9 @@ info: servers: - url: "https://localhost:8443/v1" + description: Direct standalone access + - url: "https://localhost/api/v1/autocalibration" + description: Access via Apache reverse proxy when web service is running components: schemas: diff --git a/docs/user-guide/microservices/mapping-service/api-docs/mapping-api.yaml b/docs/user-guide/microservices/mapping-service/api-docs/mapping-api.yaml index 0893f7bf30..49ff566f0f 100644 --- a/docs/user-guide/microservices/mapping-service/api-docs/mapping-api.yaml +++ b/docs/user-guide/microservices/mapping-service/api-docs/mapping-api.yaml @@ -7,6 +7,10 @@ info: description: | REST API service for 3D reconstruction with build-time model selection. + Authentication/Authorization status: + - This service currently does not implement endpoint-level AuthN/AuthZ. + - Protect access using trusted network boundaries, reverse proxy controls, and TLS. + This service provides endpoints for performing 3D reconstruction from multiple input images. The model is selected at build time: - **MapAnything**: Universal Feed-Forward Metric 3D Reconstruction - **VGGT**: Visual Geometry Grounded Transformer for sparse view reconstruction @@ -18,7 +22,7 @@ info: url: https://www.apache.org/licenses/LICENSE-2.0.html servers: - - url: https://localhost:8444 + - url: https://localhost:8444/v1 description: Direct standalone access - url: https://localhost/api/v1/mapping description: Access via Apache reverse proxy when web service is running diff --git a/docs/user-guide/microservices/mapping-service/build-from-source.md b/docs/user-guide/microservices/mapping-service/build-from-source.md index 3485fa5892..4c679fbb25 100644 --- a/docs/user-guide/microservices/mapping-service/build-from-source.md +++ b/docs/user-guide/microservices/mapping-service/build-from-source.md @@ -111,7 +111,7 @@ The response will include which model was used: ### Health Check ```bash -curl https://localhost:8444/health +curl https://localhost:8444/v1/health ``` Response includes model information: @@ -128,7 +128,7 @@ Response includes model information: ### Model Information ```bash -curl https://localhost:8444/models +curl https://localhost:8444/v1/models ``` Response shows single model details: diff --git a/docs/user-guide/microservices/mapping-service/mapping-service.md b/docs/user-guide/microservices/mapping-service/mapping-service.md index 3a64a8163d..9a788f7eac 100644 --- a/docs/user-guide/microservices/mapping-service/mapping-service.md +++ b/docs/user-guide/microservices/mapping-service/mapping-service.md @@ -43,6 +43,10 @@ sequenceDiagram ## API Endpoints +> **Security note:** Mapping service endpoints currently do not enforce endpoint-level +> authentication or authorization. Deploy behind trusted network boundaries and reverse +> proxy controls, and use TLS for transport protection. + ### Health Check ```bash @@ -180,13 +184,13 @@ python client_example.py --images image1.jpg image2.jpg --mesh-type pointcloud - ```bash # Health check -curl https://localhost:8444/health --insecure +curl https://localhost:8444/v1/health --insecure # List models -curl https://localhost:8444/models --insecure +curl https://localhost:8444/v1/models --insecure # Reconstruction with images (using multipart/form-data - recommended) -curl -X POST "https://localhost:8444/reconstruction" \ +curl -X POST "https://localhost:8444/v1/reconstruction" \ -F "images=@image1.jpg" \ -F "images=@image2.jpg" \ -F "output_format=glb" \ @@ -194,7 +198,7 @@ curl -X POST "https://localhost:8444/reconstruction" \ --insecure # Reconstruction with video -curl -X POST "https://localhost:8444/reconstruction" \ +curl -X POST "https://localhost:8444/v1/reconstruction" \ -F "video=@video.mp4" \ -F "output_format=glb" \ -F "mesh_type=mesh" \ @@ -202,7 +206,7 @@ curl -X POST "https://localhost:8444/reconstruction" \ --insecure # Reconstruction with both images and video -curl -X POST "https://localhost:8444/reconstruction" \ +curl -X POST "https://localhost:8444/v1/reconstruction" \ -F "images=@image1.jpg" \ -F "images=@image2.jpg" \ -F "video=@video.mp4" \ @@ -211,7 +215,7 @@ curl -X POST "https://localhost:8444/reconstruction" \ --insecure # Save GLB output to file (requires jq for JSON parsing) -curl -X POST "https://localhost:8444/reconstruction" \ +curl -X POST "https://localhost:8444/v1/reconstruction" \ -F "images=@image1.jpg" \ -F "images=@image2.jpg" \ -F "output_format=glb" \ diff --git a/kubernetes/scenescape-chart/templates/mapping/deployment.yaml b/kubernetes/scenescape-chart/templates/mapping/deployment.yaml index f6f2a71816..43e05283a6 100644 --- a/kubernetes/scenescape-chart/templates/mapping/deployment.yaml +++ b/kubernetes/scenescape-chart/templates/mapping/deployment.yaml @@ -42,7 +42,7 @@ spec: command: - sh - -c - - curl -k -s https://localhost:8444/health + - curl -k -s https://localhost:8444/v1/health periodSeconds: 10 timeoutSeconds: 60 failureThreshold: 5 diff --git a/manager/config/default-ssl.conf b/manager/config/default-ssl.conf index 65e88078ba..e76d665167 100644 --- a/manager/config/default-ssl.conf +++ b/manager/config/default-ssl.conf @@ -137,6 +137,9 @@ SSLProxyEngine on + # API Path Pattern in SceneScape: + # - /api/v1/{resource} → Django Manager service (native endpoints) + # - /api/v1/{service}/{path} → Proxied microservices ProxyPass /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/ ProxyPassReverse /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/ diff --git a/manager/src/manager/mesh_generator.py b/manager/src/manager/mesh_generator.py index a03fd815fd..f07e8343d3 100644 --- a/manager/src/manager/mesh_generator.py +++ b/manager/src/manager/mesh_generator.py @@ -145,7 +145,7 @@ class MappingServiceClient: def __init__(self): # Get mapping service URL from environment or use default - self.base_url = os.environ.get('MAPPING_SERVICE_URL', 'https://mapping.scenescape.intel.com:8444') + self.base_url = os.environ.get('MAPPING_SERVICE_URL', 'https://mapping.scenescape.intel.com:8444/v1') self.timeout_per_camera = 30 # timeout (in seconds) per camera for mesh generation self.health_timeout = 5 # Short timeout for health checks diff --git a/mapping/src/api_service_base.py b/mapping/src/api_service_base.py index d11f628848..f5e2ee0d7e 100644 --- a/mapping/src/api_service_base.py +++ b/mapping/src/api_service_base.py @@ -105,6 +105,7 @@ def validate_reconstruction_request(data): # Configure Flask app app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB max request size +API_PREFIX = "/v1" def initialize_model(): """Initialize the model - this will be overridden by model-specific services""" @@ -190,6 +191,7 @@ def create_glb_file(result: Dict[str, Any], mesh_type: str = "mesh") -> str: os.close(temp_glb_fd) @app.route("/reconstruction", methods=["POST"]) +@app.route(f"{API_PREFIX}/reconstruction", methods=["POST"]) def reconstruct3D(): """ Perform 3D reconstruction from multipart images OR video @@ -342,6 +344,7 @@ def worker(): @app.route("/health", methods=["GET"]) +@app.route(f"{API_PREFIX}/health", methods=["GET"]) def health_check(): """Health check endpoint""" global loaded_model, model_name @@ -357,6 +360,7 @@ def health_check(): return jsonify(health_status), 200 @app.route("/models", methods=["GET"]) +@app.route(f"{API_PREFIX}/models", methods=["GET"]) def list_models(): """List the available model and its status""" global loaded_model, model_name @@ -377,6 +381,7 @@ def list_models(): return jsonify(models_data), 200 @app.route("/reconstruction/status/", methods=["GET"]) +@app.route(f"{API_PREFIX}/reconstruction/status/", methods=["GET"]) def reconstruction_status(request_id): prune_status() status = get_status(request_id) diff --git a/mapping/src/mapping_client.py b/mapping/src/mapping_client.py new file mode 100644 index 0000000000..43ad6816f5 --- /dev/null +++ b/mapping/src/mapping_client.py @@ -0,0 +1,115 @@ +# SPDX-FileCopyrightText: (C) 2026 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import logging +import mimetypes +import os +import time +from http import HTTPStatus +from urllib.parse import urljoin + +from scene_common.rest_client import RESTClient + +logger = logging.getLogger(__name__) + + +class MappingClient(RESTClient): + """Client for mapping/reconstruction REST endpoints.""" + + def _build_multipart_files(self, data, file_fields): + """Build multipart file payload and return opened handles for caller cleanup.""" + data = data.copy() + files = [] + handles = [] + try: + for field in file_fields: + if field not in data: + continue + paths = data.pop(field) + if isinstance(paths, str): + paths = [paths] + for path in paths: + if not os.path.exists(path): + raise FileNotFoundError( + f"File not found for field '{field}': {path}") + mime_type, _ = mimetypes.guess_type(path) + if mime_type is None: + mime_type = "application/octet-stream" + fh = open(path, 'rb') + handles.append(fh) + files.append((field, (os.path.basename(path), fh, mime_type))) + except Exception: + for fh in handles: + try: + fh.close() + except Exception: + logger.warning( + "Failed to close file handle during cleanup", exc_info=True) + raise + + return data, files if files else None, handles + + def performReconstruction(self, data): + """Perform 3D reconstruction by uploading images and/or a video file.""" + handles = [] + try: + data, files, handles = self._build_multipart_files(data, ['images', 'video']) + path = urljoin(self.url, "reconstruction") + + # Do not force Content-Type for multipart requests. + headers = self._headers() + if 'Content-Type' in headers: + del headers['Content-Type'] + + data_args = self.prepareDataArgs(data, files) + reply = self.session.post( + path, + **data_args, + files=files, + headers=headers, + verify=self.verify_ssl, + timeout=self.timeout, + ) + return self.decodeReply(reply, [HTTPStatus.OK, HTTPStatus.ACCEPTED]) + finally: + for fh in handles: + fh.close() + + def getReconstructionStatus(self, request_id): + """Get status for a reconstruction request.""" + reply = self.request("get", f"reconstruction/status/{request_id}") + return self.decodeReply(reply, HTTPStatus.OK) + + def waitForReconstruction(self, request_id, timeout_s=900, poll_s=1.5): + """Poll reconstruction status until complete/failed or timeout.""" + deadline = time.time() + timeout_s + while time.time() < deadline: + status = self.getReconstructionStatus(request_id) + if not status: + raise RuntimeError( + f"Status check failed ({status.status_code}): {status.errors}") + + state = status.get("state") + if state == "complete": + result = status.get("result") or {} + if not result.get("success", True): + raise RuntimeError(result.get("error", "Reconstruction failed")) + return result + + if state == "failed": + raise RuntimeError(status.get("error") or "Reconstruction failed") + + time.sleep(poll_s) + + raise TimeoutError( + f"Timed out waiting for mesh generation (request_id={request_id}) after {timeout_s}s") + + def healthCheckEndpoint(self): + """Health check endpoint.""" + reply = self.request("get", "health") + return self.decodeReply(reply, HTTPStatus.OK) + + def listModels(self, filter=None): + """List available models.""" + reply = self.request("get", "models", params=filter) + return self.decodeReply(reply, HTTPStatus.OK) diff --git a/mapping/tools/client_example.py b/mapping/tools/client_example.py index 32a0e94d11..e8f66adffc 100644 --- a/mapping/tools/client_example.py +++ b/mapping/tools/client_example.py @@ -11,18 +11,24 @@ import base64 import json -import requests from pathlib import Path from typing import List import argparse import urllib3 import os -import time +import sys # Disable SSL warnings when using --insecure flag urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) POLL_TIMEOUT = 900 +TOOLS_DIR = Path(__file__).resolve().parent +MAPPING_SRC_DIR = TOOLS_DIR.parent / "src" +if str(MAPPING_SRC_DIR) not in sys.path: + sys.path.insert(0, str(MAPPING_SRC_DIR)) + +from mapping_client import MappingClient + def encode_image_to_base64(image_path: str) -> str: """Encode image file to base64 string""" with open(image_path, "rb") as f: @@ -31,19 +37,15 @@ def encode_image_to_base64(image_path: str) -> str: return encoded def send_reconstruction_request( - api_url: str, + client: MappingClient, image_paths: List[str], video_path: str, use_keyframes: bool = True, output_format: str = "glb", - mesh_type: str = "mesh", - verify_ssl: bool = True + mesh_type: str = "mesh" ): """Send reconstruction request to the API""" - # Prepare image data - files = [] - # Prepare request payload payload = { "output_format": output_format, @@ -56,37 +58,29 @@ def send_reconstruction_request( p = Path(img_path) if not p.exists(): raise FileNotFoundError(f"Image not found: {img_path}") - files.append(("images", (p.name, p.open("rb"), "image/jpeg"))) + payload["images"] = image_paths if video_path: p = Path(video_path) if not p.exists(): raise FileNotFoundError(f"Video not found: {video_path}") - files.append(("video", (p.name, p.open("rb"), "video/mp4"))) + payload["video"] = video_path - print(f"Sending request to {api_url}/reconstruction") + print(f"Sending request to {client.url}reconstruction") if image_paths and video_path: - print(f"- Images: {len([f for f in files if f[0] == 'images'])}") + print(f"- Images: {len(image_paths)}") print(f"- Video: {video_path}") elif image_paths: - print(f"- Images: {len(files)}") + print(f"- Images: {len(image_paths)}") else: print(f"- Video: {video_path}") print(f"- Output format: {output_format}") print(f"- Mesh type: {mesh_type}") try: - # Send POST request - response = requests.post( - f"{api_url}/reconstruction", - data=payload, - files=files, - timeout=int(os.getenv("GUNICORN_TIMEOUT", "900")), # 15 minute timeout - verify=verify_ssl - ) - - if response.status_code in (200, 202): - started = response.json() + started = client.performReconstruction(payload) + + if started.status_code in (200, 202) and not started.errors: if "processing_time" in started and started.get("success"): model_used = started.get("model", "unknown") @@ -99,7 +93,11 @@ def send_reconstruction_request( return None print(f"✅ Accepted. request_id={rid}. Polling for completion...") - final = wait_for_result(api_url, rid, verify_ssl=verify_ssl, poll_s=1.5) + final = client.waitForReconstruction( + rid, + timeout_s=int(os.getenv("GUNICORN_TIMEOUT", "900")), + poll_s=1.5, + ) model_used = final.get("model", "unknown") pt = final.get("processing_time", None) if pt is not None: @@ -109,15 +107,8 @@ def send_reconstruction_request( return final else: - print(f"❌ Error {response.status_code}: {response.text}") + print(f"❌ Error {started.status_code}: {started.errors}") return None - - except requests.exceptions.Timeout: - print("❌ Request timed out") - return None - except requests.exceptions.ConnectionError: - print("❌ Connection error - is the API server running?") - return None except Exception as e: print(f"❌ Error: {e}") return None @@ -132,25 +123,21 @@ def save_glb_file(glb_data: str, output_path: str): except Exception as e: print(f"❌ Failed to save GLB file: {e}") -def check_api_health(api_url: str, verify_ssl: bool = True): +def check_api_health(client: MappingClient): """Check API health and available models""" try: - # Health check - response = requests.get(f"{api_url}/health", timeout=10, verify=verify_ssl) - if response.status_code == 200: - health = response.json() + health = client.healthCheckEndpoint() + if health: print(f"✅ API is healthy") print(f" Device: {health['device']}") print(f" Model: {health.get('model', 'unknown')}") print(f" Model loaded: {health.get('model_loaded', False)}") else: - print(f"❌ Health check failed: {response.status_code}") + print(f"❌ Health check failed: {health.status_code}, {health.errors}") return False - # Get model info - response = requests.get(f"{api_url}/models", timeout=10, verify=verify_ssl) - if response.status_code == 200: - models = response.json() + models = client.listModels() + if models: print("📋 Model information:") model_info = models.get('model_info') if model_info: @@ -166,41 +153,10 @@ def check_api_health(api_url: str, verify_ssl: bool = True): print(f"❌ Failed to connect to API: {e}") return False -def wait_for_result(api_url: str, request_id: str, verify_ssl: bool, - timeout_s: int = POLL_TIMEOUT, poll_s: float = 1.5): - """Poll /reconstruction/status/ until complete/failed or timeout.""" - deadline = time.time() + timeout_s - status_url = f"{api_url}/reconstruction/status/{request_id}" - - while time.time() < deadline: - r = requests.get(status_url, timeout=10, verify=verify_ssl) - if not r.ok: - raise RuntimeError(f"Status check failed {r.status_code}: {r.text}") - - st = r.json() - state = st.get("state") - msg = st.get("message") or "" - err = st.get("error") - - print(f"state={state} {('- ' + msg) if msg else ''}") - - if state == "complete": - result = (st.get("result") or {}) - if not result.get("success", True): - raise RuntimeError(result.get("error", "Reconstruction failed")) - return result - - if state == "failed": - raise RuntimeError(err or "Reconstruction failed") - - time.sleep(poll_s) - - raise TimeoutError(f"Timed out waiting for mesh generation (request_id={request_id}) after {timeout_s}s") - def main(): parser = argparse.ArgumentParser(description="3D Mapping Models API Client") - parser.add_argument("--api-url", default="https://localhost:8444", - help="API server URL (default: https://localhost:8444)") + parser.add_argument("--api-url", default="https://localhost:8444/v1", + help="API server URL (default: https://localhost:8444/v1)") parser.add_argument("--video", help="Path to input video file") parser.add_argument("--images", nargs="+", @@ -221,9 +177,11 @@ def main(): # Determine SSL verification setting verify_ssl = not args.insecure + timeout_s = int(os.getenv("GUNICORN_TIMEOUT", str(POLL_TIMEOUT))) + client = MappingClient(url=args.api_url, verify_ssl=verify_ssl, timeout=timeout_s) # Check API health - if not check_api_health(args.api_url, verify_ssl=verify_ssl): + if not check_api_health(client): return 1 if args.health_check: @@ -236,13 +194,12 @@ def main(): # Send reconstruction request result = send_reconstruction_request( - args.api_url, + client, args.images, args.video, args.use_keyframes, args.format, args.mesh_type, - verify_ssl=verify_ssl ) if result and result.get("success"): diff --git a/sample_data/docker-compose-dl-streamer-example.yml b/sample_data/docker-compose-dl-streamer-example.yml index b3c614a54d..5a62f8646f 100644 --- a/sample_data/docker-compose-dl-streamer-example.yml +++ b/sample_data/docker-compose-dl-streamer-example.yml @@ -567,7 +567,7 @@ services: - source: mapping-key target: certs/scenescape-mapping.key healthcheck: - test: ["CMD", "curl", "-k", "-I", "-s", "https://localhost:8444/health"] + test: ["CMD", "curl", "-k", "-I", "-s", "https://localhost:8444/v1/health"] interval: 10s timeout: 60s retries: 5 diff --git a/scene_common/src/scene_common/client_factory.py b/scene_common/src/scene_common/client_factory.py new file mode 100644 index 0000000000..cf0f03084d --- /dev/null +++ b/scene_common/src/scene_common/client_factory.py @@ -0,0 +1,93 @@ +# SPDX-FileCopyrightText: (C) 2026 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import importlib +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Optional + +from scene_common.rest_client import RESTClient + + +@dataclass +class SceneScapeClients: + """Composed REST clients for core and optional service endpoints.""" + + core: RESTClient + autocalibration: Optional[RESTClient] = None + mapping: Optional[RESTClient] = None + + +def _add_source_paths(service_src_dirs): + if not service_src_dirs: + return + + for src_dir in service_src_dirs: + p = Path(src_dir) + if p.exists() and str(p) not in sys.path: + sys.path.insert(0, str(p)) + + +def _load_client_class(module_name, class_name): + module = importlib.import_module(module_name) + return getattr(module, class_name) + + +def create_scenescape_clients( + base_url, + token=None, + auth=None, + verify_ssl=False, + timeout=10, + include_autocalibration=True, + include_mapping=True, + service_src_dirs=None, + strict_imports=True): + """Create a composed set of SceneScape clients. + + This composes service-specific clients, but endpoint methods remain defined + only in each service folder's client module. + """ + _add_source_paths(service_src_dirs) + + clients = SceneScapeClients( + core=RESTClient( + url=f"{base_url}/api/v1", + token=token, + auth=auth, + verify_ssl=verify_ssl, + timeout=timeout, + ), + ) + + if include_autocalibration: + try: + auto_cls = _load_client_class( + "autocalibration_client", "AutoCalibrationClient") + clients.autocalibration = auto_cls( + url=f"{base_url}/api/v1/autocalibration", + token=token, + auth=auth, + verify_ssl=verify_ssl, + timeout=timeout, + ) + except Exception: + if strict_imports: + raise + + if include_mapping: + try: + mapping_cls = _load_client_class("mapping_client", "MappingClient") + clients.mapping = mapping_cls( + url=f"{base_url}/api/v1/mapping/", + token=token, + auth=auth, + verify_ssl=verify_ssl, + timeout=timeout, + ) + except Exception: + if strict_imports: + raise + + return clients diff --git a/scene_common/src/scene_common/rest_client.py b/scene_common/src/scene_common/rest_client.py index a180302f8b..b5b1a2e728 100644 --- a/scene_common/src/scene_common/rest_client.py +++ b/scene_common/src/scene_common/rest_client.py @@ -715,59 +715,3 @@ def importScene(self, zip_file_path): files = {"zipFile": (os.path.basename(zip_file_path), f)} return self._create(endpoint, data={}, files=files) - # Auto-calibration - def getStatus(self): - """Gets system status - - @return RESTResult with system status on success, - empty with `errors` set on failure - """ - return self._get("status", None) - - def registerScene(self, sceneId, data): - """Register a scene for auto-calibration - - @param sceneId ID of the scene to register - @param data dict with registration parameters - @return RESTResult with registration info on success, - empty with `errors` set on failure - """ - return self._create(f"scenes/{sceneId}/registration", data) - - def getSceneRegistrationStatus(self, sceneId): - """Gets scene registration status - - @param sceneId ID of the scene - @return RESTResult with registration status on success, - empty with `errors` set on failure - """ - return self._get(f"scenes/{sceneId}/registration", None) - - def updateSceneRegistration(self, sceneId, data): - """Updates scene registration - - @param sceneId ID of the scene - @param data dict with registration update parameters - @return RESTResult with updated registration on success, - empty with `errors` set on failure - """ - return self._update(f"scenes/{sceneId}/registration", data) - - def calibrateCamera(self, cameraId, data): - """Calibrate a camera - - @param cameraId ID of the camera to calibrate - @param data dict with calibration parameters - @return RESTResult with calibration info on success, - empty with `errors` set on failure - """ - return self._create(f"cameras/{cameraId}/calibration", data) - - def getCameraCalibrationStatus(self, cameraId): - """Gets camera calibration status - - @param cameraId ID of the camera - @return RESTResult with calibration status on success, - empty with `errors` set on failure - """ - return self._get(f"cameras/{cameraId}/calibration", None) diff --git a/tests/api/conftest.py b/tests/api/conftest.py index 0868e69522..90cca9dfa6 100644 --- a/tests/api/conftest.py +++ b/tests/api/conftest.py @@ -12,7 +12,12 @@ import time import requests import os -from mapping_client import MappingClient +from pathlib import Path + +ROOT_DIR = Path(__file__).resolve().parents[2] +MAPPING_SRC_DIR = ROOT_DIR / "mapping" / "src" +AUTOCALIB_SRC_DIR = ROOT_DIR / "autocalibration" / "src" +from scene_common.client_factory import create_scenescape_clients from scene_common.rest_client import RESTClient @@ -168,16 +173,26 @@ def token(base_url, username, password): return api_token @pytest.fixture(scope='session') -def http_client(token, base_url) -> RESTClient: - return RESTClient(url=f"{base_url}/api/v1", token=token, verify_ssl=False) +def service_clients(token, base_url): + return create_scenescape_clients( + base_url=base_url, + token=token, + verify_ssl=False, + service_src_dirs=[AUTOCALIB_SRC_DIR, MAPPING_SRC_DIR], + strict_imports=True, + ) + +@pytest.fixture(scope='session') +def http_client(service_clients) -> RESTClient: + return service_clients.core @pytest.fixture(scope='session') -def autocalib_client(token, base_url) -> RESTClient: - return RESTClient(url=f"{base_url}/api/v1/autocalibration", token=token, verify_ssl=False) +def autocalib_client(service_clients): + return service_clients.autocalibration @pytest.fixture(scope='session') -def mapping_client(token, base_url) -> MappingClient: - return MappingClient(url=f"{base_url}/api/v1/mapping/", token=token, verify_ssl=False) +def mapping_client(service_clients): + return service_clients.mapping @pytest.fixture(scope='session') def api_map(http_client, autocalib_client, mapping_client): diff --git a/tests/api/mapping_client.py b/tests/api/mapping_client.py deleted file mode 100644 index a2ad14d6bd..0000000000 --- a/tests/api/mapping_client.py +++ /dev/null @@ -1,125 +0,0 @@ -# SPDX-FileCopyrightText: (C) 2026 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -import logging -import mimetypes -import os -from http import HTTPStatus -from urllib.parse import urljoin - -from scene_common.rest_client import RESTClient - -logger = logging.getLogger(__name__) - - -class MappingClient(RESTClient): - """Client for mapping/reconstruction REST endpoints. - - Extends RESTClient with mapping-specific methods that don't belong - in the general-purpose REST client. - """ - - def _build_multipart_files(self, data, file_fields): - """Extract file path strings from data dict and open them as file handles - for multipart/form-data requests. - - Caller MUST close the returned handles (use try/finally). - - @param data dict potentially containing file path strings - @param file_fields list of field names to treat as file paths; - each value may be a single path string or a list of paths - @return (cleaned_data, files_list, file_handles) - - cleaned_data: data dict with file fields removed - - files_list: list of (field, (filename, handle, mimetype)) - tuples ready to pass as requests `files=` - - file_handles: list of open file handles to close after request - """ - data = data.copy() - files = [] - handles = [] - try: - for field in file_fields: - if field not in data: - continue - paths = data.pop(field) - if isinstance(paths, str): - paths = [paths] - for path in paths: - if not os.path.exists(path): - raise FileNotFoundError( - f"File not found for field '{field}': {path}") - mime_type, _ = mimetypes.guess_type(path) - if mime_type is None: - mime_type = "application/octet-stream" - fh = open(path, 'rb') - handles.append(fh) - files.append((field, (os.path.basename(path), fh, mime_type))) - except Exception: - for fh in handles: - try: - fh.close() - except Exception: - logger.warning( - "Failed to close file handle during cleanup", exc_info=True) - raise - - return data, files if files else None, handles - - def performReconstruction(self, data): - """Perform 3D reconstruction by uploading images and/or a video file. - - Sends a multipart/form-data POST to /reconstruction. - - @param data dict with reconstruction parameters: - - images (str or list[str]): image file paths (field name "images") - - video (str or list[str]): video file path(s) (field name "video") - - output_format (str): "glb" or "json" - - mesh_type (str): "mesh" or "pointcloud" - - use_keyframes (bool): True/False - `images` and `video` are opened as binary multipart parts; - all remaining keys become plain form fields. - @return dict with reconstruction info on success, - empty with `errors` set on failure - """ - handles = [] - try: - data, files, handles = self._build_multipart_files( - data, ['images', 'video']) - full_path = urljoin(self.url, "reconstruction") - headers = {'Authorization': f"Token {self.token}"} - data_args = self.prepareDataArgs(data, files) - reply = self.session.post(full_path, **data_args, files=files, - headers=headers, verify=self.verify_ssl) - return self.decodeReply(reply, [HTTPStatus.OK, HTTPStatus.ACCEPTED]) - finally: - for fh in handles: - fh.close() - - def getReconstructionStatus(self, request_id): - """Poll the status of an async reconstruction job. - - @param request_id request_id returned by performReconstruction - @return dict with job state on success: - - state: "queued" | "processing" | "complete" | "failed" - - message: optional progress message - - result: final result dict (when state == "complete") - - error: error description (when state == "failed") - """ - return self._get(f"reconstruction/status/{request_id}", None) - - def healthCheckEndpoint(self): - """Health check endpoint - - @return dict with health status on success, - empty with `errors` set on failure - """ - return self._get("health", None) - - def listModels(self, filter=None): - """List available models - - @param filter Optional dict with filter parameters - @return dict with list of models on success, - empty with `errors` set on failure - """ - return self._get("models", filter) diff --git a/tests/api/test_sscape_api.py b/tests/api/test_sscape_api.py index e0c6f29282..3eb6885ce1 100644 --- a/tests/api/test_sscape_api.py +++ b/tests/api/test_sscape_api.py @@ -3,7 +3,6 @@ import logging import os -import sys import json import glob import time @@ -11,11 +10,8 @@ import pytest TESTS_API_DIR = os.path.dirname(__file__) -if TESTS_API_DIR not in sys.path: - sys.path.insert(0, TESTS_API_DIR) from scene_common.rest_client import RESTClient -from mapping_client import MappingClient # Logging Configuration LOG_FILE = os.path.join(os.path.dirname(__file__), "api_test.log") @@ -148,7 +144,7 @@ def normalize_file_paths(data): return os.path.normpath(os.path.join(TESTS_API_DIR, data)) return data -def build_call_kwargs(request_data, api_client=None): +def build_call_kwargs(request_data, api_name=None): """ Normalise the structured request dict from the JSON scenario into a flat kwargs dict ready to be splatted into the API method call. @@ -158,8 +154,8 @@ def build_call_kwargs(request_data, api_client=None): Each key is unpacked directly as a kwarg. body request payload; forwarded as kwarg "data". - api_client is used to skip file resolution for MappingClient, which opens - its own file paths internally via _build_multipart_files. + api_name is used to skip file resolution for mapping API, which opens + its own file paths internally. """ kwargs = {} @@ -171,8 +167,8 @@ def build_call_kwargs(request_data, api_client=None): else: logger.warning(f" 'path_params' should be a dict, got {type(value).__name__}; skipping") elif key == "body": - # MappingClient opens its own files from path strings; skip resolution - if isinstance(api_client, MappingClient): + # Mapping API opens its own files from path strings; skip resolution + if api_name == "mapping": kwargs["data"] = normalize_file_paths(value) else: kwargs["data"] = resolve_file_paths(value) @@ -272,7 +268,7 @@ def execute_step(api_map, step, step_number, total_steps): # Normalize request keys to match RESTClient parameter names: # "body" -> "data" (request body) # "path_params" -> extract and merge its contents into request_data - call_kwargs = build_call_kwargs(raw_request, api_client=api) + call_kwargs = build_call_kwargs(raw_request, api_name=api_name) open_files = [v for v in (call_kwargs.get("data") or {}).values() if hasattr(v, "read")] diff --git a/tests/compose/mapping.yml b/tests/compose/mapping.yml index dd935ac254..3d7dfc37b0 100644 --- a/tests/compose/mapping.yml +++ b/tests/compose/mapping.yml @@ -49,7 +49,7 @@ services: - source: mapping-key target: certs/scenescape-mapping.key healthcheck: - test: ["CMD", "curl", "-k", "-I", "-s", "https://localhost:8444/health"] + test: ["CMD", "curl", "-k", "-I", "-s", "https://localhost:8444/v1/health"] interval: 10s timeout: 60s retries: 5 From b926d45b07fb8b5b03521501aca5e7b52125e849 Mon Sep 17 00:00:00 2001 From: Sarat Poluri Date: Sun, 31 May 2026 19:25:16 -0700 Subject: [PATCH 10/17] Remove copilot test instruction redundancy --- .github/copilot-instructions.md | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 05a54fc393..3450b085f0 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -129,33 +129,15 @@ make rebuild-core # Clean + build (useful after code changes) ## Testing Framework -**For comprehensive test creation guidance, see `.github/skills/testing/SKILL.md`** - detailed instructions on creating unit, functional, integration, UI, and smoke tests with both positive and negative cases. +Testing guidance is intentionally centralized in skills to avoid duplication. -**Test infrastructure** is fully pytest-based. Docker Compose lifecycle is managed by session-scoped fixtures in `tests/conftest.py`. Tests declare their service requirements via a module-level `SCENESCAPE_SPEC` using `FuncTestSpec` + `ServiceProfile`. +- Canonical test authoring and categorization guidance: `.github/skills/testing/SKILL.md` +- Canonical runtime verification and completion rules: `.github/skills/test-verification-gate/SKILL.md` -**Running Tests** (from repo root): +At this level, only rely on high-level routing: -```bash -make setup-tests # Build test images, secrets, venv -make run_basic_acceptance_tests # Smoke tests (functional + ui + unit + stability) -make run_standard_tests # Functional + UI + security + stability -make run_functional_tests # Functional tests only -make run_ui_tests # UI/Selenium tests only -make run_unit_tests # Unit tests only (sscape_tests) -make run_metric_tests # Tracker quality metrics -make run_performance_tests # Inference performance + geometry -make run_stability_tests HOURS=24 # Long-running stability -``` - -**Running tests directly with pytest** (from repo root, with tests/.venv activated): - -```bash -pytest tests/sscape_tests # Unit tests -pytest tests/functional # All functional tests -pytest tests/functional/test_roi_mqtt.py # Single functional test -pytest tests/ -m basic_acceptance # Smoke suite only -pytest tests/ --junitxml=results.xml 2>&1 | tee output.log # Save results to file -``` +- Test infrastructure is pytest-based with Docker Compose lifecycle managed in `tests/conftest.py`. +- Prefer root `Makefile` test targets for execution unless a narrower, explicit pytest invocation is required. ### Completion Gate For Test Tasks (Critical) From 4030f61959591b50732ebd8ecfd3c92ead1c1fc2 Mon Sep 17 00:00:00 2001 From: Sarat Poluri Date: Mon, 1 Jun 2026 14:04:25 -0700 Subject: [PATCH 11/17] Fix prettier issues --- sample_data/docker-compose-dl-streamer-example.yml | 3 ++- tests/compose/mapping.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sample_data/docker-compose-dl-streamer-example.yml b/sample_data/docker-compose-dl-streamer-example.yml index b240349959..bb5438728f 100644 --- a/sample_data/docker-compose-dl-streamer-example.yml +++ b/sample_data/docker-compose-dl-streamer-example.yml @@ -567,7 +567,8 @@ services: - source: mapping-key target: certs/scenescape-mapping.key healthcheck: - test: ["CMD", "curl", "-k", "-I", "-s", "https://localhost:8444/v1/health"] + test: + ["CMD", "curl", "-k", "-I", "-s", "https://localhost:8444/v1/health"] interval: 10s timeout: 60s retries: 5 diff --git a/tests/compose/mapping.yml b/tests/compose/mapping.yml index 3d7dfc37b0..57d0aae934 100644 --- a/tests/compose/mapping.yml +++ b/tests/compose/mapping.yml @@ -49,7 +49,8 @@ services: - source: mapping-key target: certs/scenescape-mapping.key healthcheck: - test: ["CMD", "curl", "-k", "-I", "-s", "https://localhost:8444/v1/health"] + test: + ["CMD", "curl", "-k", "-I", "-s", "https://localhost:8444/v1/health"] interval: 10s timeout: 60s retries: 5 From 33344a0dc877024db6751e8a72722545aa0d7c90 Mon Sep 17 00:00:00 2001 From: Sarat Poluri Date: Mon, 1 Jun 2026 14:18:39 -0700 Subject: [PATCH 12/17] Apply suggestions from copilot code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Makefile | 2 +- .../microservices/mapping-service/mapping-service.md | 3 ++- scene_common/src/scene_common/client_factory.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index f3847c7c06..b0d315f40d 100644 --- a/Makefile +++ b/Makefile @@ -725,7 +725,7 @@ init-secrets: $(SECRETSDIR) certificates auth-secrets $(SECRETSDIR): mkdir -p $@ @if ! chmod go-rwx $(SECRETSDIR); then \ - if [ "$${CI}" = "true" ]; then \ + if [ "$${CI}" = "true" ] || [ "$${CI}" = "1" ]; then \ echo "Warning: could not set restrictive permissions on $(SECRETSDIR) in CI; secrets may be more exposed on this filesystem. Ensure runner isolation controls are in place."; \ else \ exit 1; \ diff --git a/docs/user-guide/microservices/mapping-service/mapping-service.md b/docs/user-guide/microservices/mapping-service/mapping-service.md index 9a788f7eac..b7474ad37a 100644 --- a/docs/user-guide/microservices/mapping-service/mapping-service.md +++ b/docs/user-guide/microservices/mapping-service/mapping-service.md @@ -150,9 +150,10 @@ data = { "mesh_type": "mesh", } +try: try: # Send request through the Apache reverse proxy used in the full stack deployment - response = requests.post("https://localhost/api/v1/mapping/reconstruction", data=data, files=files) + response = requests.post("https://localhost/api/v1/mapping/reconstruction", data=data, files=files, verify=False) result = response.json() if result["success"]: diff --git a/scene_common/src/scene_common/client_factory.py b/scene_common/src/scene_common/client_factory.py index cf0f03084d..ee2853832b 100644 --- a/scene_common/src/scene_common/client_factory.py +++ b/scene_common/src/scene_common/client_factory.py @@ -43,7 +43,7 @@ def create_scenescape_clients( include_autocalibration=True, include_mapping=True, service_src_dirs=None, - strict_imports=True): + strict_imports=False): """Create a composed set of SceneScape clients. This composes service-specific clients, but endpoint methods remain defined From 5fa6bfcee5bf85e571366b25e9d14770ca4bf0f5 Mon Sep 17 00:00:00 2001 From: Sarat Poluri Date: Mon, 1 Jun 2026 14:26:39 -0700 Subject: [PATCH 13/17] Remove old routes --- mapping/src/api_service_base.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mapping/src/api_service_base.py b/mapping/src/api_service_base.py index f5e2ee0d7e..217221274d 100644 --- a/mapping/src/api_service_base.py +++ b/mapping/src/api_service_base.py @@ -190,7 +190,6 @@ def create_glb_file(result: Dict[str, Any], mesh_type: str = "mesh") -> str: finally: os.close(temp_glb_fd) -@app.route("/reconstruction", methods=["POST"]) @app.route(f"{API_PREFIX}/reconstruction", methods=["POST"]) def reconstruct3D(): """ @@ -343,7 +342,6 @@ def worker(): return jsonify({"success": True, "request_id": request_id, "state": "processing"}), 200 -@app.route("/health", methods=["GET"]) @app.route(f"{API_PREFIX}/health", methods=["GET"]) def health_check(): """Health check endpoint""" @@ -359,7 +357,6 @@ def health_check(): log.debug(f"Health check: {health_status}") return jsonify(health_status), 200 -@app.route("/models", methods=["GET"]) @app.route(f"{API_PREFIX}/models", methods=["GET"]) def list_models(): """List the available model and its status""" @@ -380,7 +377,6 @@ def list_models(): } return jsonify(models_data), 200 -@app.route("/reconstruction/status/", methods=["GET"]) @app.route(f"{API_PREFIX}/reconstruction/status/", methods=["GET"]) def reconstruction_status(request_id): prune_status() From 9ac9e30748159dd18bbfe0b02b2f6321b7a7bba3 Mon Sep 17 00:00:00 2001 From: Sarat Poluri Date: Tue, 2 Jun 2026 12:15:23 -0700 Subject: [PATCH 14/17] Connect to proper endpoint with socketio --- autocalibration/src/auto_camera_calibration_api.py | 2 +- manager/config/default-ssl.conf | 8 ++++++-- manager/config/webserver-init | 4 ++-- sample_data/docker-compose-dl-streamer-example.yml | 3 +++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/autocalibration/src/auto_camera_calibration_api.py b/autocalibration/src/auto_camera_calibration_api.py index f8d4ad4f61..081b87f5a5 100644 --- a/autocalibration/src/auto_camera_calibration_api.py +++ b/autocalibration/src/auto_camera_calibration_api.py @@ -130,7 +130,7 @@ def __init__(self, calibrationContext=None): self.app.config['MAX_CONTENT_LENGTH'] = self.MAX_REQUEST_SIZE self.calibrationContext = calibrationContext - self.socketio = SocketIO(self.app, cors_allowed_origins=["*"]) + self.socketio = SocketIO(self.app, path="/v1/socket.io/", cors_allowed_origins=["*"]) if self.calibrationContext is not None: self.calibrationContext.socketio = self.socketio self.socket_client = {} diff --git a/manager/config/default-ssl.conf b/manager/config/default-ssl.conf index e76d665167..4ff3a57d5c 100644 --- a/manager/config/default-ssl.conf +++ b/manager/config/default-ssl.conf @@ -136,6 +136,8 @@ # downgrade-1.0 force-response-1.0 SSLProxyEngine on + SSLProxyCACertificateFile /run/secrets/certs/scenescape-ca.pem + SSLProxyCheckPeerName off # API Path Pattern in SceneScape: # - /api/v1/{resource} → Django Manager service (native endpoints) @@ -149,8 +151,10 @@ ProxyPass /mqtt wss://broker.scenescape.intel.com:1884/ timeout=10 ProxyPassReverse /mqtt wss://broker.scenescape.intel.com:1884/ - ProxyPass "/api/v1/autocalibration/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/socket.io/" - ProxyPassReverse "/api/v1/autocalibration/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/socket.io/" + + ProxyPass wss://autocalibration.scenescape.intel.com:8443/v1/socket.io/ + ProxyPassReverse wss://autocalibration.scenescape.intel.com:8443/v1/socket.io/ + WSGIPassAuthorization On diff --git a/manager/config/webserver-init b/manager/config/webserver-init index 91ec909f5c..5ba39a35f0 100755 --- a/manager/config/webserver-init +++ b/manager/config/webserver-init @@ -73,8 +73,8 @@ if [ -n "${KUBERNETES_SERVICE_HOST}" ] ; then ProxyPassReverse /mqtt wss://broker.scenescape.intel.com:1884/ \ ProxyPass /api/v1/autocalibration/ https://autocalibration.scenescape.intel.com:8443/v1/ \ ProxyPassReverse /api/v1/autocalibration/ https://autocalibration.scenescape.intel.com:8443/v1/ \ - ProxyPass "/api/v1/autocalibration/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/socket.io/" \ - ProxyPassReverse "/api/v1/autocalibration/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/socket.io/" \ + ProxyPass "/api/v1/autocalibration/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/v1/socket.io/" \ + ProxyPassReverse "/api/v1/autocalibration/socket.io/" "wss://autocalibration.scenescape.intel.com:8443/v1/socket.io/" \ \n WSGIPassAuthorization On' /etc/apache2/sites-available/000-default.conf fi diff --git a/sample_data/docker-compose-dl-streamer-example.yml b/sample_data/docker-compose-dl-streamer-example.yml index bb5438728f..4d15ef03f8 100644 --- a/sample_data/docker-compose-dl-streamer-example.yml +++ b/sample_data/docker-compose-dl-streamer-example.yml @@ -458,8 +458,11 @@ services: - EGL_PLATFORM=surfaceless - NETVLAD_MODEL_DIR=/usr/local/lib/python3.11/site-packages/third_party/netvlad - HTTP_PROXY=${HTTP_PROXY} + - http_proxy=${http_proxy} - HTTPS_PROXY=${HTTPS_PROXY} + - https_proxy=${https_proxy} - NO_PROXY=${NO_PROXY},scenescape.intel.com + - no_proxy=${no_proxy},scenescape.intel.com volumes: - vol-media:/home/scenescape/SceneScape/media - vol-datasets:/home/scenescape/SceneScape/datasets From 3f5e36e8dbcdf113ac1a3cd010fc178909c30d50 Mon Sep 17 00:00:00 2001 From: "Belhaik, Sadek" Date: Wed, 3 Jun 2026 15:30:03 +0200 Subject: [PATCH 15/17] init shell variable from Make variable --- common.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/common.mk b/common.mk index bd03d1e470..55d837c620 100644 --- a/common.mk +++ b/common.mk @@ -31,6 +31,7 @@ build-image: $(BUILD_DIR) Dockerfile @{ \ set -xe; \ set -o pipefail; \ + EXTRA_BUILD_ARGS="$(EXTRA_BUILD_ARGS)"; \ if [ "$(GHCR_CACHE)" = "true" ]; then \ EXTRA_BUILD_ARGS+=" --cache-from type=registry,ref=ghcr.io/${CACHE_REGISTRY}/$(IMAGE):main"; \ if [ "$(WRITE_CACHE)" = "true" ]; then \ From ba56a5730e6b4d23cc558d6fff42e41c1f221495 Mon Sep 17 00:00:00 2001 From: "Belhaik, Sadek" Date: Wed, 3 Jun 2026 15:30:34 +0200 Subject: [PATCH 16/17] fix MODEL_TYPE --- sample_data/docker-compose-dl-streamer-example.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample_data/docker-compose-dl-streamer-example.yml b/sample_data/docker-compose-dl-streamer-example.yml index 01d31fc568..781ce53121 100644 --- a/sample_data/docker-compose-dl-streamer-example.yml +++ b/sample_data/docker-compose-dl-streamer-example.yml @@ -533,7 +533,7 @@ services: profiles: - experimental - mapping - image: scenescape-mapping:${VERSION:-latest} + image: scenescape-mapping-${MAPPING_MODEL:-mapanything}:${VERSION:-latest} init: true user: "${USER_UID:-1000}:${USER_GID:-1000}" networks: From 45fb116ac4d748ff4da82a4faafe9a229b2ee871 Mon Sep 17 00:00:00 2001 From: Sarat Poluri Date: Wed, 3 Jun 2026 13:23:49 -0700 Subject: [PATCH 17/17] Add the version extension to the mapping proxy endpoints --- manager/config/default-ssl.conf | 4 ++-- manager/config/webserver-init | 4 ++-- sample_data/docker-compose-dl-streamer-example.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/manager/config/default-ssl.conf b/manager/config/default-ssl.conf index 4ff3a57d5c..f8c0d6d02f 100644 --- a/manager/config/default-ssl.conf +++ b/manager/config/default-ssl.conf @@ -142,8 +142,8 @@ # API Path Pattern in SceneScape: # - /api/v1/{resource} → Django Manager service (native endpoints) # - /api/v1/{service}/{path} → Proxied microservices - ProxyPass /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/ - ProxyPassReverse /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/ + ProxyPass /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/v1/ + ProxyPassReverse /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/v1/ ProxyPass /api/v1/autocalibration/ https://autocalibration.scenescape.intel.com:8443/v1/ ProxyPassReverse /api/v1/autocalibration/ https://autocalibration.scenescape.intel.com:8443/v1/ diff --git a/manager/config/webserver-init b/manager/config/webserver-init index 5ba39a35f0..eb28954ae6 100755 --- a/manager/config/webserver-init +++ b/manager/config/webserver-init @@ -67,8 +67,8 @@ fi if [ -n "${KUBERNETES_SERVICE_HOST}" ] ; then sed -i '/<\/VirtualHost>/i \ SSLProxyEngine on \ - ProxyPass /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/ \ - ProxyPassReverse /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/ \ + ProxyPass /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/v1/ \ + ProxyPassReverse /api/v1/mapping/ https://mapping.scenescape.intel.com:8444/v1/ \ ProxyPass /mqtt wss://broker.scenescape.intel.com:1884/ \ ProxyPassReverse /mqtt wss://broker.scenescape.intel.com:1884/ \ ProxyPass /api/v1/autocalibration/ https://autocalibration.scenescape.intel.com:8443/v1/ \ diff --git a/sample_data/docker-compose-dl-streamer-example.yml b/sample_data/docker-compose-dl-streamer-example.yml index 781ce53121..4f8228db8b 100644 --- a/sample_data/docker-compose-dl-streamer-example.yml +++ b/sample_data/docker-compose-dl-streamer-example.yml @@ -455,8 +455,8 @@ services: - http_proxy=${http_proxy} - HTTPS_PROXY=${HTTPS_PROXY} - https_proxy=${https_proxy} - - NO_PROXY=${NO_PROXY},scenescape.intel.com - - no_proxy=${no_proxy},scenescape.intel.com + - NO_PROXY=${NO_PROXY},.scenescape.intel.com + - no_proxy=${no_proxy},.scenescape.intel.com volumes: - vol-media:/home/scenescape/SceneScape/media - vol-datasets:/home/scenescape/SceneScape/datasets