bổ sung thêm hàm tự resign token SAS
This commit is contained in:
+381
-8
@@ -22,6 +22,8 @@ from rasterio.transform import from_bounds
|
||||
import asyncio
|
||||
import hashlib
|
||||
import traceback
|
||||
import socket
|
||||
import urllib.request
|
||||
|
||||
# Import report generator
|
||||
from report_generator import generate_training_report, generate_prediction_report
|
||||
@@ -107,6 +109,58 @@ DEFAULT_LABEL_NAMES = {
|
||||
}
|
||||
|
||||
|
||||
# ============ NETWORK CONNECTIVITY HELPERS ============
|
||||
def check_internet_connectivity(timeout=5):
|
||||
"""
|
||||
Check if we have internet connectivity to common hosts
|
||||
Returns: (is_connected: bool, error_message: str)
|
||||
"""
|
||||
test_hosts = [
|
||||
("8.8.8.8", 53), # Google DNS
|
||||
("1.1.1.1", 53), # Cloudflare DNS
|
||||
]
|
||||
|
||||
for host, port in test_hosts:
|
||||
try:
|
||||
socket.setdefaulttimeout(timeout)
|
||||
socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port))
|
||||
return True, None
|
||||
except (socket.timeout, socket.error):
|
||||
continue
|
||||
|
||||
return False, "No internet connectivity detected. Please check your network connection."
|
||||
|
||||
|
||||
def check_planetary_computer_access(timeout=10):
|
||||
"""
|
||||
Check if we can access Microsoft Planetary Computer
|
||||
Returns: (is_accessible: bool, error_message: str)
|
||||
"""
|
||||
test_urls = [
|
||||
"https://planetarycomputer.microsoft.com/api/stac/v1",
|
||||
]
|
||||
|
||||
for url in test_urls:
|
||||
try:
|
||||
req = urllib.request.Request(url)
|
||||
response = urllib.request.urlopen(req, timeout=timeout)
|
||||
if response.status == 200:
|
||||
return True, None
|
||||
except urllib.error.HTTPError as e:
|
||||
# 405 Method Not Allowed is OK - means the service is reachable
|
||||
if e.code in [200, 405]:
|
||||
return True, None
|
||||
return False, f"HTTP Error {e.code}: {e.reason}"
|
||||
except urllib.error.URLError as e:
|
||||
if "Could not resolve host" in str(e):
|
||||
return False, f"Cannot resolve Planetary Computer hosts. DNS issue detected: {str(e)}"
|
||||
return False, f"URL Error: {str(e)}"
|
||||
except Exception as e:
|
||||
return False, f"Unexpected error: {str(e)}"
|
||||
|
||||
return False, "Cannot access Microsoft Planetary Computer. Service may be down or blocked by firewall."
|
||||
|
||||
|
||||
class TrainingConfig(BaseModel):
|
||||
"""Cấu hình training - Tất cả bắt buộc nhập từ giao diện"""
|
||||
# Khu vực (bbox)
|
||||
@@ -233,6 +287,44 @@ async def change_detection_page():
|
||||
return HTMLResponse("<h2>Change Detection Interface not found.</h2>")
|
||||
|
||||
|
||||
@app.get("/api/network/check")
|
||||
async def check_network():
|
||||
"""
|
||||
Check network connectivity and access to Planetary Computer
|
||||
Useful for diagnosing connection issues before attempting data downloads
|
||||
"""
|
||||
result = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"internet_connected": False,
|
||||
"planetary_computer_accessible": False,
|
||||
"internet_error": None,
|
||||
"pc_error": None,
|
||||
"status": "unknown"
|
||||
}
|
||||
|
||||
# Check basic internet connectivity
|
||||
is_connected, conn_error = check_internet_connectivity(timeout=5)
|
||||
result["internet_connected"] = is_connected
|
||||
result["internet_error"] = conn_error
|
||||
|
||||
# Check Planetary Computer access
|
||||
if is_connected:
|
||||
is_accessible, access_error = check_planetary_computer_access(timeout=10)
|
||||
result["planetary_computer_accessible"] = is_accessible
|
||||
result["pc_error"] = access_error
|
||||
|
||||
if is_accessible:
|
||||
result["status"] = "ready"
|
||||
else:
|
||||
result["status"] = "pc_unavailable"
|
||||
else:
|
||||
result["status"] = "no_internet"
|
||||
result["planetary_computer_accessible"] = False
|
||||
result["pc_error"] = "Cannot check - no internet connection"
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def root():
|
||||
"""Serve main index page with tabs"""
|
||||
@@ -1234,6 +1326,11 @@ async def run_prediction(config: PredictionConfig):
|
||||
else: # temporal or extended
|
||||
bands_to_load = ["B02", "B03", "B04", "B08", "B11", "SCL"]
|
||||
|
||||
# Retry logic for data loading
|
||||
max_retries = 3
|
||||
s2_data = None
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
s2_data = load(
|
||||
s2_items,
|
||||
bbox=bbox,
|
||||
@@ -1242,6 +1339,38 @@ async def run_prediction(config: PredictionConfig):
|
||||
groupby="solar_day",
|
||||
resolution=config.resolution
|
||||
).compute()
|
||||
break
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
|
||||
is_auth_error = any(err in error_msg for err in [
|
||||
"AuthenticationFailed", "Signature fields not well formed",
|
||||
"Server failed to authenticate", "403", "401"
|
||||
])
|
||||
|
||||
if is_auth_error:
|
||||
print(f"⚠️ Authentication error. Re-signing items...")
|
||||
try:
|
||||
import planetary_computer
|
||||
s2_items = [planetary_computer.sign(item) for item in s2_items]
|
||||
if attempt < max_retries - 1:
|
||||
import time
|
||||
time.sleep(1)
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if "does not exist" in error_msg or "RasterioIOError" in error_msg:
|
||||
if len(s2_items) > 3 and attempt < max_retries - 1:
|
||||
remove_count = max(1, len(s2_items) // 5)
|
||||
s2_items = s2_items[:-remove_count]
|
||||
print(f"⚠️ Access error. Reduced to {len(s2_items)} scenes, retrying...")
|
||||
continue
|
||||
if attempt == max_retries - 1:
|
||||
raise ValueError(f"Failed to load Sentinel-2 data: {error_msg[:200]}")
|
||||
|
||||
if s2_data is None:
|
||||
raise ValueError("Failed to load Sentinel-2 data after retries")
|
||||
|
||||
prediction_status["progress"] = "Đã load Sentinel-2 data"
|
||||
|
||||
@@ -1261,6 +1390,8 @@ async def run_prediction(config: PredictionConfig):
|
||||
|
||||
if s1_items:
|
||||
s1_items = s1_items[:config.max_scenes]
|
||||
# Try to load Sentinel-1 with error handling
|
||||
try:
|
||||
s1_data = load(
|
||||
s1_items,
|
||||
bbox=bbox,
|
||||
@@ -1275,6 +1406,9 @@ async def run_prediction(config: PredictionConfig):
|
||||
vv_data = 10 * np.log10(s1_data['vv'].where(s1_data['vv'] > 0))
|
||||
use_radar = True
|
||||
prediction_status["progress"] = f"Đã load Sentinel-1 data ({len(s1_items)} scenes)"
|
||||
except Exception as s1_error:
|
||||
print(f"⚠️ Failed to load Sentinel-1 data: {str(s1_error)[:100]}")
|
||||
prediction_status["progress"] = "Lỗi Sentinel-1, bỏ qua radar features"
|
||||
else:
|
||||
prediction_status["progress"] = "Không có dữ liệu Sentinel-1, bỏ qua radar features"
|
||||
except Exception as e:
|
||||
@@ -2647,8 +2781,13 @@ async def change_detection_predict_workflow(
|
||||
if len(items) == 0:
|
||||
raise HTTPException(status_code=404, detail="No Sentinel-2 data found for the given area and date range")
|
||||
|
||||
# Load data
|
||||
# Load data with error handling
|
||||
signed_items = [planetary_computer.sign(item) for item in items]
|
||||
|
||||
max_retries = 3
|
||||
data = None
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
data = odc.stac.load(
|
||||
signed_items,
|
||||
bbox=bbox,
|
||||
@@ -2656,6 +2795,41 @@ async def change_detection_predict_workflow(
|
||||
resolution=resolution,
|
||||
chunks={"x": 2048, "y": 2048}
|
||||
).compute()
|
||||
break
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
|
||||
is_auth_error = any(err in error_msg for err in [
|
||||
"AuthenticationFailed", "Signature fields not well formed",
|
||||
"Server failed to authenticate", "403", "401"
|
||||
])
|
||||
|
||||
if is_auth_error:
|
||||
print(f"⚠️ Authentication error. Re-signing items...")
|
||||
try:
|
||||
signed_items = [planetary_computer.sign(item) for item in items]
|
||||
if attempt < max_retries - 1:
|
||||
import time
|
||||
time.sleep(1)
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if ("does not exist" in error_msg or "RasterioIOError" in error_msg) and len(signed_items) > 3:
|
||||
remove_count = max(1, len(signed_items) // 5)
|
||||
signed_items = signed_items[:-remove_count]
|
||||
items = items[:-remove_count]
|
||||
print(f"⚠️ Access error. Reduced to {len(signed_items)} scenes, retrying...")
|
||||
if attempt < max_retries - 1:
|
||||
continue
|
||||
if attempt == max_retries - 1:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to load Sentinel-2 data: {error_msg[:200]}"
|
||||
)
|
||||
|
||||
if data is None:
|
||||
raise HTTPException(status_code=500, detail="Failed to load Sentinel-2 data")
|
||||
|
||||
# --- STEP 3: CALCULATE NDVI ---
|
||||
print("[CHANGE DETECTION] Calculating NDVI...")
|
||||
@@ -3931,6 +4105,24 @@ async def ndvi_predict_timeseries(config: NDVIPredictionConfig):
|
||||
bbox = [min_lon, min_lat, max_lon, max_lat]
|
||||
time_range = f"{config.start_date}/{config.end_date}"
|
||||
|
||||
# Check network connectivity first
|
||||
print(f"\n🌐 Checking network connectivity...")
|
||||
is_connected, conn_error = check_internet_connectivity()
|
||||
if not is_connected:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail=f"❌ {conn_error}\n\n"
|
||||
f"Please check:\n"
|
||||
f"1. Your internet connection is active\n"
|
||||
f"2. DNS servers are configured correctly\n"
|
||||
f"3. Firewall/proxy settings allow outbound connections"
|
||||
)
|
||||
|
||||
is_accessible, access_error = check_planetary_computer_access()
|
||||
if not is_accessible:
|
||||
print(f"⚠️ {access_error}")
|
||||
print(f" Will attempt to load data anyway (may fail)...")
|
||||
|
||||
# Load Sentinel-2 time series using Planetary Computer
|
||||
print(f"\n📡 Loading Sentinel-2 data from Planetary Computer...")
|
||||
|
||||
@@ -3984,8 +4176,85 @@ async def ndvi_predict_timeseries(config: NDVIPredictionConfig):
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if "Could not resolve host" in error_msg or "CURL error" in error_msg:
|
||||
|
||||
# Check for various error types that might indicate problematic scenes
|
||||
is_network_error = any(err in error_msg for err in [
|
||||
"Could not resolve host", "CURL error", "Connection", "Timeout"
|
||||
])
|
||||
is_access_error = any(err in error_msg for err in [
|
||||
"does not exist in the file system", "RasterioIOError",
|
||||
"not recognized as a supported dataset", "Aborting load"
|
||||
])
|
||||
is_auth_error = any(err in error_msg for err in [
|
||||
"AuthenticationFailed", "Signature fields not well formed",
|
||||
"Server failed to authenticate", "403", "401"
|
||||
])
|
||||
|
||||
if is_auth_error:
|
||||
print(f"⚠️ Authentication error detected. Re-signing items and retrying...")
|
||||
# Re-sign all items with fresh tokens
|
||||
try:
|
||||
import planetary_computer
|
||||
s2_items = [planetary_computer.sign(item) for item in s2_items]
|
||||
print(f" Re-signed {len(s2_items)} items")
|
||||
|
||||
if attempt < max_retries - 1:
|
||||
import time
|
||||
time.sleep(1) # Brief pause before retry
|
||||
continue
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"❌ Authentication failed after {max_retries} attempts.\n\n"
|
||||
f"Azure Blob Storage authentication is failing. This may be due to:\n"
|
||||
f"1. Expired or invalid SAS tokens from Planetary Computer\n"
|
||||
f"2. Planetary Computer API service issues\n"
|
||||
f"3. System clock synchronization issues (check date/time)\n\n"
|
||||
f"Please try again in a few minutes. If the problem persists,\n"
|
||||
f"Microsoft Planetary Computer may be experiencing issues.\n\n"
|
||||
f"Error: {error_msg[:300]}"
|
||||
)
|
||||
except Exception as resign_error:
|
||||
print(f" Re-signing failed: {resign_error}")
|
||||
if attempt == max_retries - 1:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to re-sign authentication tokens: {str(resign_error)}"
|
||||
)
|
||||
|
||||
elif is_access_error and len(s2_items) > 5:
|
||||
# If we have access errors and multiple scenes, try with fewer scenes
|
||||
print(f"⚠️ Data access error detected. Reducing scene count and retrying...")
|
||||
# Remove last 20% of scenes and try again
|
||||
remove_count = max(1, len(s2_items) // 5)
|
||||
s2_items = s2_items[:-remove_count]
|
||||
print(f" Reduced to {len(s2_items)} scenes")
|
||||
|
||||
if attempt < max_retries - 1:
|
||||
continue
|
||||
else:
|
||||
# Final attempt with reduced scenes
|
||||
print(f" Final attempt with {len(s2_items)} scenes...")
|
||||
|
||||
elif is_network_error:
|
||||
print(f"⚠️ Network error on attempt {attempt + 1}: {error_msg[:100]}")
|
||||
|
||||
# Re-check connectivity on network errors
|
||||
if "Could not resolve host" in error_msg:
|
||||
is_conn, conn_msg = check_internet_connectivity()
|
||||
if not is_conn:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail=f"❌ Network connectivity lost!\n\n"
|
||||
f"{conn_msg}\n\n"
|
||||
f"Please:\n"
|
||||
f"1. Check your internet connection\n"
|
||||
f"2. Verify DNS settings (try 8.8.8.8 or 1.1.1.1)\n"
|
||||
f"3. Check firewall/proxy settings\n"
|
||||
f"4. Try again once connection is restored\n\n"
|
||||
f"Original error: {error_msg[:150]}"
|
||||
)
|
||||
|
||||
if attempt < max_retries - 1:
|
||||
import time
|
||||
print(f" Retrying in {retry_delay} seconds...")
|
||||
@@ -3994,13 +4263,30 @@ async def ndvi_predict_timeseries(config: NDVIPredictionConfig):
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail=f"Network error: Unable to download Sentinel-2 data after {max_retries} attempts. "
|
||||
f"Please check your internet connection or try again later. "
|
||||
detail=f"❌ Network error persists after {max_retries} attempts.\n\n"
|
||||
f"Unable to download Sentinel-2 data from Microsoft Planetary Computer.\n\n"
|
||||
f"Possible causes:\n"
|
||||
f"1. Unstable internet connection\n"
|
||||
f"2. DNS resolution issues\n"
|
||||
f"3. Planetary Computer service temporarily unavailable\n"
|
||||
f"4. Firewall blocking access to sentinel2l2a01.blob.core.windows.net\n\n"
|
||||
f"Please check your network and try again.\n\n"
|
||||
f"Error: {error_msg[:200]}"
|
||||
)
|
||||
else:
|
||||
# Non-network error, raise immediately
|
||||
raise
|
||||
# If still failing after retries, try with minimal data
|
||||
if attempt == max_retries - 1 and len(s2_items) > 3:
|
||||
print(f"⚠️ Multiple failures. Attempting with minimal scenes (3)...")
|
||||
s2_items = s2_items[:3]
|
||||
continue
|
||||
else:
|
||||
# Last resort: provide meaningful error
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to load Sentinel-2 data. This may be due to expired access tokens "
|
||||
f"or unavailable data. Please try a different time range or area. "
|
||||
f"Error: {error_msg[:300]}"
|
||||
)
|
||||
|
||||
if s2_data is None:
|
||||
raise HTTPException(status_code=500, detail="Failed to load Sentinel-2 data")
|
||||
@@ -4365,7 +4651,14 @@ async def ndvi_forecast(config: NDVIForecastConfig):
|
||||
|
||||
print(f"✅ Found {len(s2_items)} historical scenes")
|
||||
|
||||
# Load data
|
||||
# Load data with retry logic for network and access errors
|
||||
max_retries = 3
|
||||
retry_delay = 2
|
||||
s2_data = None
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
print(f"📥 Loading Sentinel-2 data (attempt {attempt + 1}/{max_retries})...")
|
||||
s2_data = load(
|
||||
s2_items,
|
||||
bbox=bbox,
|
||||
@@ -4376,6 +4669,86 @@ async def ndvi_forecast(config: NDVIForecastConfig):
|
||||
).compute()
|
||||
|
||||
print(f"✅ Loaded {len(s2_data.time)} time steps")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
|
||||
# Check for various error types
|
||||
is_network_error = any(err in error_msg for err in [
|
||||
"Could not resolve host", "CURL error", "Connection", "Timeout"
|
||||
])
|
||||
is_access_error = any(err in error_msg for err in [
|
||||
"does not exist in the file system", "RasterioIOError",
|
||||
"not recognized as a supported dataset", "Aborting load"
|
||||
])
|
||||
is_auth_error = any(err in error_msg for err in [
|
||||
"AuthenticationFailed", "Signature fields not well formed",
|
||||
"Server failed to authenticate", "403", "401"
|
||||
])
|
||||
|
||||
if is_auth_error:
|
||||
print(f"⚠️ Authentication error. Re-signing items...")
|
||||
try:
|
||||
import planetary_computer
|
||||
s2_items = [planetary_computer.sign(item) for item in s2_items]
|
||||
if attempt < max_retries - 1:
|
||||
import time
|
||||
time.sleep(1)
|
||||
continue
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Authentication failed: {error_msg[:200]}"
|
||||
)
|
||||
except Exception as resign_error:
|
||||
if attempt == max_retries - 1:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to re-sign tokens: {str(resign_error)}"
|
||||
)
|
||||
|
||||
elif is_access_error and len(s2_items) > 5:
|
||||
# If we have access errors and multiple scenes, try with fewer scenes
|
||||
print(f"⚠️ Data access error detected. Reducing scene count and retrying...")
|
||||
remove_count = max(1, len(s2_items) // 5)
|
||||
s2_items = s2_items[:-remove_count]
|
||||
print(f" Reduced to {len(s2_items)} scenes")
|
||||
|
||||
if attempt < max_retries - 1:
|
||||
continue
|
||||
else:
|
||||
print(f" Final attempt with {len(s2_items)} scenes...")
|
||||
|
||||
elif is_network_error:
|
||||
print(f"⚠️ Network error on attempt {attempt + 1}: {error_msg[:100]}")
|
||||
if attempt < max_retries - 1:
|
||||
import time
|
||||
print(f" Retrying in {retry_delay} seconds...")
|
||||
time.sleep(retry_delay)
|
||||
retry_delay *= 2
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail=f"Network error: Unable to download Sentinel-2 data after {max_retries} attempts. "
|
||||
f"Error: {error_msg[:200]}"
|
||||
)
|
||||
else:
|
||||
# Try with minimal scenes on final attempt
|
||||
if attempt == max_retries - 1 and len(s2_items) > 3:
|
||||
print(f"⚠️ Multiple failures. Attempting with minimal scenes (3)...")
|
||||
s2_items = s2_items[:3]
|
||||
continue
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to load Sentinel-2 data. This may be due to expired access tokens "
|
||||
f"or unavailable data. Please try a different time range or area. "
|
||||
f"Error: {error_msg[:300]}"
|
||||
)
|
||||
|
||||
if s2_data is None:
|
||||
raise HTTPException(status_code=500, detail="Failed to load Sentinel-2 data")
|
||||
|
||||
# Calculate spectral indices
|
||||
print(f"\n📊 Calculating spectral indices...")
|
||||
@@ -4476,7 +4849,7 @@ async def ndvi_forecast(config: NDVIForecastConfig):
|
||||
if len(point_features) == 0:
|
||||
print("[NDVI FORECAST][FALLBACK] No valid classification points. Falling back to simple seasonal forecast.")
|
||||
force_simple_forecast = True
|
||||
|
||||
else:
|
||||
print(f"✅ Extracted features for {len(point_features)} valid points")
|
||||
|
||||
# Classify points
|
||||
|
||||
Reference in New Issue
Block a user