Skip to content
Snippets Groups Projects
Commit cd625af1 authored by jaannigu's avatar jaannigu
Browse files

container automatic shutdown, refactoring

parent 32254958
No related branches found
No related tags found
No related merge requests found
......@@ -48,6 +48,9 @@ oauth.register(
client_kwargs={'scope': SCOPES},
)
# ------------------------------------------------------------------------------
# ROUTES
# ------------------------------------------------------------------------------
......@@ -147,9 +150,8 @@ def launch():
return redirect(url_for('index'))
container_image = request.args.get('containerImage', '<No containerImage>')
#public_ip = ac.run_ecs_container_fargate(container_image)
public_ip = ac.run_ecs_container_fargate(container_image)
time.sleep(7) # Give more time for the container and app to load
time.sleep(5) # Give more time for the container and app to load
if public_ip.startswith('Task '):
return jsonify({"error": public_ip})
elif public_ip.startswith("Error"):
......@@ -199,10 +201,11 @@ def logout():
Stops the ECS Fargate container and clears the local session.
"""
container_ip = request.args.get('ip')
ac.stop_ecs_container_fargate(container_ip)
stopped = ac.stop_ecs_container_fargate(container_ip)
session.pop('user', None)
resp = make_response(redirect(url_for('index')))
resp.set_cookie('my_token', '', expires=0)
print(f"{stopped}; ip: {container_ip}")
return resp
......
......@@ -5,9 +5,13 @@ COPY environment.yml /tmp/environment.yml
RUN conda env create -f /tmp/environment.yml --name atlas && \
conda clean --all -f -y
WORKDIR /app
COPY app.py /app/app.py
COPY verification.py /app/verification.py
COPY dashboard.py /app/dashboard.py
COPY setup_callbacks.py /app/setup_callbacks.py
COPY data /app/data
EXPOSE 5006
CMD ["/bin/bash", "-c", "source activate atlas && panel serve app.py --address=0.0.0.0 --port=5006 --allow-websocket-origin=*"]
CMD ["/bin/bash", "-c", "source activate atlas && panel serve app.py --address=0.0.0.0 --port=5006 --setup setup_callbacks.py --unused-session-lifetime 300000 --check-unused-sessions 60000 --allow-websocket-origin=*"]
import intake
import xarray as xr
import pandas as pd
import numpy as np
import geopandas as gpd
import cartopy.crs as ccrs
import hvplot.xarray # noqa
import hvplot.pandas # noqa
from geoviews.tile_sources import EsriImagery
from pyproj import CRS
import panel as pn
from dashboard import *
import numpy as np
import plotly.graph_objs as go
import holoviews as hv
import jwt, requests
from verification import is_token_valid, update, logout
pn.extension("plotly")
hv.extension('bokeh')
data_load_failed = False
errormsg = ""
try:
geoms = gpd.read_file('data/geometries/countries/CNTR_RG_20M_2024_4326.shp')
geoms = geoms[(geoms['EU_STAT']=='T') | (geoms['EFTA_STAT']=='T')]
geoms = geoms[['NAME_ENGL', 'ISO3_CODE', 'geometry']]
temperature = pd.read_parquet("data/parquet/t2m_2023_v2.parquet").set_index('date')[['mean_corrected','country']]
precipitation = pd.read_parquet("data/parquet/tp_2013.parquet").set_index('date')[['mean_corrected','country']]
temperature_zarr = xr.open_zarr("data/zarr/temp_2023_short.zarr")
precipitation
precipitation_country_mean = precipitation.groupby('country').sum()*12
temperature_country_mean = temperature.groupby('country').mean()
country_list = precipitation_country_mean[precipitation_country_mean['mean_corrected']>0].index
geoms=geoms[geoms['ISO3_CODE'].isin(country_list)]
def get_prec_va(prec_array, x):
rval = prec_array[prec_array.index==x]['mean_corrected'].values
if len(rval)>0:
return(rval[0])
else:
return 0
geoms['tp'] = geoms['ISO3_CODE'].apply(lambda x: get_prec_va(precipitation_country_mean,x))
geoms['t2m'] = geoms['ISO3_CODE'].apply(lambda x: get_prec_va(temperature_country_mean,x))
except Exception as e:
data_load_failed = True
errormsg = f"Error loading data: {e}"
print(errormsg)
token = pn.state.session_args.get('token', [None])[0]
container_ip = pn.state.session_args.get('ip', [None])[0]
token = None
container_ip = None
is_valid = None
err_msg = None
if token and isinstance(token, bytes):
token = token.decode("utf-8")
if container_ip and isinstance(container_ip, bytes):
container_ip = container_ip.decode("utf-8")
is_valid, err_msg = is_token_valid(token)
def build_app():
token = pn.state.session_args.get('token', [None])[0]
container_ip = pn.state.session_args.get('ip', [None])[0]
if not is_valid:
fallback_layout = pn.Column(
pn.pane.Markdown("## User not authenticated"),
pn.pane.Markdown(f"Please log in from your main site. Autherror: {err_msg} Token: {token}")
)
fallback_layout.servable()
if isinstance(token, bytes):
token = token.decode('utf-8')
if isinstance(container_ip, bytes):
container_ip = container_ip.decode('utf-8')
return run_dashboard(token, container_ip)
elif data_load_failed:
fallback_layout = pn.Column(
pn.pane.Markdown("## Data is not available"),
pn.pane.Markdown(f"```\n{errormsg}\n```")
)
fallback_layout.servable()
else:
cookie = pn.pane.HTML()
update(cookie, token)
selector_country = pn.widgets.Select(
description="Select country",
value='Latvia',
name="Country",
# options=list(geoms['NAME_ENGL'].values),
options=list(geoms['NAME_ENGL'])
)
selector_parameter = pn.widgets.Select(
description="Select parameter",
value='tp',
name="parameter",
# options=list(geoms['NAME_ENGL'].values),
options=['t2m', 'tp']
)
selector_timestep = pn.widgets.FloatSlider(
value=0, start=0, end=len(temperature_zarr.time), step=1, name="Timestep"
)
pn.extension()
main_layout = build_app()
main_layout.servable()
logout_pane = pn.pane.HTML()
logout_button = pn.widgets.Button(name="Logout", button_type="danger")
def on_logout_click(event):
script = logout(container_ip)
logout_pane.object = script
def plot_country_timeseries(country, parameter):
if parameter=='tp':
vvv = precipitation
elif parameter=='t2m':
vvv = temperature
else:
raise ValueError(f"Country {country} not supported")
country_p = vvv[vvv['country']==geoms[geoms['NAME_ENGL']==country]['ISO3_CODE'].values[0]]
surface=go.Line(x=country_p.index, y=country_p['mean_corrected'])
fig = go.Figure(data=[surface])
fig.update_layout(
title=f"ERA5 {country} {parameter}",
width=500,
height=500
)
return fig
def plot_country_polygons(color_param):
hood_plot = geoms.hvplot(geo=True, alpha=.5, c=color_param, legend=False, frame_height=400, tiles=True)
return hood_plot
def eu_plot_inter(step, param, dataset):
#return dataset.to(hv.Image, dynamic=True)
raster = dataset.isel({'time': int(step)}).hvplot(
geo=True, alpha=0.5, legend=True, frame_height=400, tiles=True, cmap='rainbow'
)
return raster.opts(
hooks=[], active_tools=['pan', 'wheel_zoom'], framewise=False
)
ifunc_timeseries = pn.bind(plot_country_timeseries, country=selector_country, parameter=selector_parameter)
ifunc_polygons = pn.bind(plot_country_polygons, color_param=selector_parameter)
ifunc_rasters = pn.bind(eu_plot_inter, step=selector_timestep, param='tp', dataset=temperature_zarr)
logout_button.on_click(on_logout_click)
pn.Row(
pn.Column(
selector_country, selector_parameter,ifunc_timeseries, selector_timestep,logout_button,logout_pane, cookie),
pn.Column(ifunc_polygons, ifunc_rasters)).servable()
import intake
import xarray as xr
import pandas as pd
import numpy as np
import geopandas as gpd
import cartopy.crs as ccrs
import hvplot.xarray # noqa
import hvplot.pandas # noqa
from geoviews.tile_sources import EsriImagery
from pyproj import CRS
import panel as pn
import numpy as np
import plotly.graph_objs as go
import holoviews as hv
import jwt, requests
from verification import is_token_valid, update_cookie, logout
def run_dashboard(token, container_ip):
pn.extension("plotly")
hv.extension('bokeh')
is_valid, err_msg = is_token_valid(token)
if not is_valid:
return pn.Column(
pn.pane.Markdown("## User not authenticated"),
pn.pane.Markdown(f"Please log in from your main site. Autherror: {err_msg} Token: {token}")
)
data_load_failed = False
errormsg = ""
try:
geoms = gpd.read_file('data/geometries/countries/CNTR_RG_20M_2024_4326.shp')
geoms = geoms[(geoms['EU_STAT']=='T') | (geoms['EFTA_STAT']=='T')]
geoms = geoms[['NAME_ENGL', 'ISO3_CODE', 'geometry']]
temperature = pd.read_parquet("data/parquet/t2m_2023_v2.parquet").set_index('date')[['mean_corrected','country']]
precipitation = pd.read_parquet("data/parquet/tp_2013.parquet").set_index('date')[['mean_corrected','country']]
temperature_zarr = xr.open_zarr("data/zarr/temp_2023_short.zarr")
precipitation
precipitation_country_mean = precipitation.groupby('country').sum()*12
temperature_country_mean = temperature.groupby('country').mean()
country_list = precipitation_country_mean[precipitation_country_mean['mean_corrected']>0].index
geoms=geoms[geoms['ISO3_CODE'].isin(country_list)]
def get_prec_va(prec_array, x):
rval = prec_array[prec_array.index==x]['mean_corrected'].values
if len(rval)>0:
return(rval[0])
else:
return 0
geoms['tp'] = geoms['ISO3_CODE'].apply(lambda x: get_prec_va(precipitation_country_mean,x))
geoms['t2m'] = geoms['ISO3_CODE'].apply(lambda x: get_prec_va(temperature_country_mean,x))
except Exception as e:
data_load_failed = True
errormsg = f"Error loading data: {e}"
print(errormsg)
if data_load_failed:
return pn.Column(
pn.pane.Markdown("## Data is not available"),
pn.pane.Markdown(f"```\n{errormsg}\n```")
)
else:
cookie = pn.pane.HTML()
update_cookie(cookie, token)
selector_country = pn.widgets.Select(
description="Select country",
value='Latvia',
name="Country",
# options=list(geoms['NAME_ENGL'].values),
options=list(geoms['NAME_ENGL'])
)
selector_parameter = pn.widgets.Select(
description="Select parameter",
value='tp',
name="parameter",
# options=list(geoms['NAME_ENGL'].values),
options=['t2m', 'tp']
)
selector_timestep = pn.widgets.FloatSlider(
value=0, start=0, end=len(temperature_zarr.time), step=1, name="Timestep"
)
logout_pane = pn.pane.HTML()
logout_button = pn.widgets.Button(name="Logout", button_type="danger")
def on_logout_click(event):
script = logout(container_ip)
logout_pane.object = script
def plot_country_timeseries(country, parameter):
if parameter=='tp':
vvv = precipitation
elif parameter=='t2m':
vvv = temperature
else:
raise ValueError(f"Country {country} not supported")
country_p = vvv[vvv['country']==geoms[geoms['NAME_ENGL']==country]['ISO3_CODE'].values[0]]
surface=go.Line(x=country_p.index, y=country_p['mean_corrected'])
fig = go.Figure(data=[surface])
fig.update_layout(
title=f"ERA5 {country} {parameter}",
width=500,
height=500
)
return fig
def plot_country_polygons(color_param):
hood_plot = geoms.hvplot(geo=True, alpha=.5, c=color_param, legend=False, frame_height=400, tiles=True)
return hood_plot
def eu_plot_inter(step, param, dataset):
#return dataset.to(hv.Image, dynamic=True)
raster = dataset.isel({'time': int(step)}).hvplot(
geo=True, alpha=0.5, legend=True, frame_height=400, tiles=True, cmap='rainbow'
)
return raster.opts(
hooks=[], active_tools=['pan', 'wheel_zoom'], framewise=False
)
ifunc_timeseries = pn.bind(plot_country_timeseries, country=selector_country, parameter=selector_parameter)
ifunc_polygons = pn.bind(plot_country_polygons, color_param=selector_parameter)
ifunc_rasters = pn.bind(eu_plot_inter, step=selector_timestep, param='tp', dataset=temperature_zarr)
logout_button.on_click(on_logout_click)
layout = pn.Row(
pn.Column(
selector_country, selector_parameter,ifunc_timeseries, selector_timestep,logout_button,logout_pane, cookie),
pn.Column(ifunc_polygons, ifunc_rasters))
return layout
#run_dashboard("","localhost:5006").servable()
\ No newline at end of file
import panel as pn
import requests
import logging
import sys
FORMAT = "%(asctime)s | %(levelname)s | %(name)s | %(message)s"
def reconfig_basic_config(format_=FORMAT, level=logging.INFO):
logging.basicConfig(format=format_, level=level, force=True)
logging.info("Logging.basicConfig completed successfully")
logging.info(pn.__version__)
reconfig_basic_config()
logger = logging.getLogger(name="app")
session_data = {}
def session_created(session_context):
# Called when session is created
# Stores token and the container's IP for function session_destroyed
session_id = session_context.id
token = pn.state.session_args.get('token', [None])[0]
ip = pn.state.session_args.get('ip', [None])[0]
if isinstance(token, bytes):
token = token.decode('utf-8')
if isinstance(ip, bytes):
ip = ip.decode('utf-8')
session_data[session_id] = {"token": token, "ip": ip}
logger.info(f"Session created: {session_id} with ip={ip}")
def session_destroyed(session_context):
# Called when the session is destroyed or unused session exceeds limit
session_id = session_context.id
info = session_data.pop(session_id, None)
logger.info("Fired session_destroyed")
if info and info["ip"]:
ip = info["ip"]
url = f"http://localhost:5000/logout?ip={ip}"
try:
r = requests.get(url, timeout=5)
logger.info(f"Stop container response: {r.status_code}, {r.text}")
except Exception as e:
logger.info(f"Error stopping container: {e}")
# Exit with code 0, which also stops the container
logger.info("Stopping container forcibly")
sys.exit(15)
else:
logger.info("No container IP known; can't stop container.")
pn.state.on_session_created(session_created)
pn.state.on_session_destroyed(session_destroyed)
import jwt, requests, cryptography
import panel as pn
from panel.io.server import get_server
COGNITO_REGION = "eu-north-1"
USER_POOL_ID = "eu-north-1_z6hZRjOrR"
COGNITO_ISS = f"https://cognito-idp.{COGNITO_REGION}.amazonaws.com/{USER_POOL_ID}"
......@@ -8,7 +9,7 @@ CLIENT_ID = "8pif30qbufd1cuamlofrffgf"
response = requests.get(JWKS_URL)
jwks = response.json()["keys"]
# 2. Helper: find the correct public key for the token's 'kid'
# Helper: find the correct public key for the token's 'kid'
def get_signing_key(token_kid):
for key in jwks:
if key["kid"] == token_kid:
......@@ -71,7 +72,7 @@ def is_token_valid(tok):
# Return False + the exception message
return (False, str(e) + jwt.__version__ + " "+jwt.__file__)
def update(html,token):
def update_cookie(html,token):
html.object = f"""
<script>
var t = encodeURIComponent("{token}");
......@@ -87,5 +88,5 @@ def logout(container_ip):
document.cookie = "token=; path=/; Max-Age=0";
window.location.href = "http://localhost:5000/logout?ip={container_ip}";
</script>
<p>Logging out... redirecting to localhost:5000</p>
"""
\ No newline at end of file
"""
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment