# Copyright (C) 2021- Swedish Meteorological and Hydrological Institute (SMHI)
#
# This file is part of baltrad-exchange.
#
# baltrad-exchange is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# baltrad-exchange is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with baltrad-exchange. If not, see <http://www.gnu.org/licenses/>.
###############################################################################
## Handler that uses the backend for executing requests
## @file
## @author Anders Henja, SMHI
## @date 2021-08-18
import shutil
from tempfile import NamedTemporaryFile
import sys
from bexchange.web import auth
from bexchange.web import util as webutil
import datetime
from http import client as httplibclient
import urllib.parse as urlparse
from bexchange.net.exceptions import DuplicateException
from .util import (
HttpConflict,
HttpNotAcceptable,
HttpForbidden,
HttpNotFound,
JsonResponse,
NoContentResponse,
Response,
TemporaryRedirectResponse
)
import json
import logging
logger = logging.getLogger("bexchange.handler")
[docs]
def post_file(ctx):
"""Receive a file from some party
:param ctx: the request context
:type ctx: :class:`~.util.RequestContext`
:return: :class:`~.util.JsonResponse` with status
*200 OK*
See :ref:`doc-rest-cmd-store-file` for details
"""
logger.debug("bexchange.handler.post_file(ctx)")
if ctx.is_anonymous(): # We don't want unauthorized messages in here unless it has been explicitly allowed
logger.info("post_file: anonymous calls are not allowed")
return Response("", status=httplibclient.UNAUTHORIZED)
with NamedTemporaryFile(dir=ctx.backend.get_tmp_folder()) as tmp:
shutil.copyfileobj(ctx.request.stream, tmp)
tmp.flush()
try:
metadata = ctx.backend.store_file(tmp.name, ctx.backend.get_auth_manager().get_nodename(ctx.request))
except LookupError as e:
raise HttpNotAcceptable(str(e))
except DuplicateException as e:
raise HttpConflict("duplicate file entry: %s"%str(e))
return Response("", status=httplibclient.OK)
[docs]
def post_dex_file(ctx):
logger.debug("bexchange.handler.post_dex_file(ctx)")
if ctx.is_anonymous(): # We don't want unauthorized messages in here unless it has been explicitly allowed
logger.info("post_dex_file: anonymous calls are not allowed")
return Response("", status=httplibclient.UNAUTHORIZED)
with NamedTemporaryFile() as tmp:
shutil.copyfileobj(ctx.request.stream, tmp)
tmp.flush()
try:
metadata = ctx.backend.store_file(tmp.name, ctx.backend.get_auth_manager().get_nodename(ctx.request))
except LookupError as e:
raise HttpNotAcceptable(str(e))
except DuplicateException as e:
raise HttpConflict("duplicate file entry: %s"%str(e))
return Response("", status=httplibclient.OK)
[docs]
def post_json_message(ctx):
"""A trigger message used to trigger different jobs from the outside
:param ctx: the request context
:type ctx: :class:`~.util.RequestContext`
:return: :class:`~.util.JsonResponse` with status
*201 Created* and metadata in body
:raise: :class:`~.util.HttpConflict` when file already
stored
See :ref:`doc-rest-cmd-post-json-message` for details
"""
logger.debug("bexchange.handler.post_json_message(ctx)")
if ctx.is_anonymous():
logger.info("post_json_message: anonymous calls are not allowed")
return Response("", status=httplibclient.UNAUTHORIZED)
data = ctx.request.get_json_data()
ctx.backend.post_message(data, ctx.backend.get_auth_manager().get_nodename(ctx.request))
return Response("", status=httplibclient.OK)
[docs]
def get_statistics(ctx):
"""Returns the statistics for modules / sources
:param ctx: the request context
:type ctx: :class:`~.util.RequestContext`
:return: :class:`~.util.JsonResponse` with status
*200 Created* and information in body
See :ref:`doc-rest-cmd-get_statistics` for details
"""
logger.debug("bexchange.handler.get_statistics(ctx)")
if ctx.is_anonymous():
logger.info("get_statistics: anonymous calls are not allowed")
return Response("", status=httplibclient.UNAUTHORIZED)
data = ctx.request.get_json_data()
stats = ctx.backend.get_statistics_manager().get_statistics(ctx.backend.get_auth_manager().get_nodename(ctx.request), data)
return Response(stats, status=httplibclient.OK)
[docs]
def list_statistic_ids(ctx):
"""Returns the statistic ids
:param ctx: the request context
:type ctx: :class:`~.util.RequestContext`
:return: :class:`~.util.JsonResponse` with status
*200 Created* and information in body
See :ref:`doc-rest-cmd-list_statistic_ids` for details
"""
logger.debug("bexchange.handler.list_statistic_ids(ctx)")
if ctx.is_anonymous():
logger.info("list_statistic_ids: anonymous calls are not allowed")
return Response("", status=httplibclient.UNAUTHORIZED)
stats = ctx.backend.get_statistics_manager().list_statistic_ids(ctx.backend.get_auth_manager().get_nodename(ctx.request))
return Response(stats, status=httplibclient.OK)
[docs]
def get_server_uptime(ctx):
"""
:returns the server uptime
See :ref:`doc-rest-cmd-server_info` for details
"""
logger.debug("bexchange.handler.get_server_uptime(ctx)")
if ctx.is_anonymous():
logger.info("get_server_uptime: anonymous calls are not allowed")
return Response("", status=httplibclient.UNAUTHORIZED)
days, hours, minutes, seconds = ctx.backend.get_server_uptime()
return Response(json.dumps({"days":days, "hours":hours, "minutes":minutes, "seconds":seconds}), status=httplibclient.OK)
[docs]
def get_server_nodename(ctx):
"""
:returns the server nodename
See :ref:`doc-rest-cmd-server_info` for details
"""
logger.debug("bexchange.handler.get_server_nodename(ctx)")
if ctx.is_anonymous():
logger.info("get_server_nodename: anonymous calls are not allowed")
return Response("", status=httplibclient.UNAUTHORIZED)
nodename = ctx.backend.get_server_nodename()
return Response(json.dumps({"nodename":nodename}), status=httplibclient.OK)
[docs]
def get_server_publickey(ctx):
"""
:returns the server publickey
See :ref:`doc-rest-cmd-server_info` for details
"""
logger.debug("bexchange.handler.get_server_publickey(ctx)")
if ctx.is_anonymous():
logger.info("get_server_publickey: anonymous calls are not allowed")
return Response("", status=httplibclient.UNAUTHORIZED)
publickey = ctx.backend.get_server_publickey()
return Response(publickey, status=httplibclient.OK)
[docs]
def file_arrival(ctx):
"""Returns if a file with specified source, object type has arrived within limit
:param ctx: the request context
:type ctx: :class:`~.util.RequestContext`
:return: :class:`~.util.JsonResponse` with status
*200 Created* and information in body
See :ref:`doc-rest-cmd-file-arrival` for details
"""
logger.debug("bexchange.handler.file_arrival(ctx)")
if ctx.is_anonymous():
logger.info("file_arrival: anonymous calls are not allowed")
return Response("", status=httplibclient.UNAUTHORIZED)
data = ctx.request.get_json_data()
source = None
object_type = None
limit = 5
if "source" in data:
source = data["source"]
if "object_type" in data:
object_type = data["object_type"]
if "limit" in data:
limit = int(data["limit"])
dtfilterdate = (datetime.datetime.utcnow() - datetime.timedelta(minutes=limit))
dtfilter = "datetime>=%s"%((datetime.datetime.utcnow() - datetime.timedelta(minutes=limit)).strftime("%Y%m%d%H%M"))
querydata = {"spid":"server-incomming", "sources":source, "object_type":object_type, "dtfilter":dtfilter}
stats = ctx.backend.get_statistics_manager().get_statistics_entries(ctx.backend.get_auth_manager().get_nodename(ctx.request), querydata)
result={"status":"ERROR"}
if stats and len(stats) > 0:
result={"status":"OK"}
return Response(json.dumps(result), status=httplibclient.OK)
[docs]
def supervise(ctx):
"""Provides functionality for supervising the node
:param ctx: the request context
:type ctx: :class:`~.util.RequestContext`
:return: :class:`~.util.JsonResponse` with status
*200 Created* and information in body
See :ref:`doc-rest-cmd-file-arrival` for details
"""
logger.debug("bexchange.handler.supervise(ctx)")
if ctx.is_anonymous():
logger.info("supervise: anonymous calls are not allowed")
return Response("", status=httplibclient.UNAUTHORIZED)
data = ctx.request.get_json_data()
source = None
origins = None
object_type = None
limit = 5
entrylimit = 0
delay = 0
count = 1
if "source" in data:
source = data["source"]
if "origins" in data:
origins = data["origins"]
if "object_type" in data:
object_type = data["object_type"]
if "limit" in data:
limit = int(data["limit"])
if "entrylimit" in data:
entrylimit = int(data["entrylimit"])
if "delay" in data:
delay = int(data["delay"])
if "count" in data:
count = int(data["count"])
if count == 0:
count = 1
dtfilterdate = (datetime.datetime.utcnow() - datetime.timedelta(seconds=limit))
dtfilter = "datetime>=%s"%((datetime.datetime.utcnow() - datetime.timedelta(seconds=limit)).strftime("%Y%m%d%H%M%S"))
if entrylimit > 0:
dtfilter = dtfilter + "&&entrytime>=%s"%((datetime.datetime.utcnow() - datetime.timedelta(seconds=entrylimit)).strftime("%Y%m%d%H%M%S"))
if delay > 0:
dtfilter = dtfilter + "&&delay<=%d"%delay
querydata = {"spid":"server-incomming", "sources":source, "origins":origins, "object_type":object_type, "filter":dtfilter}
stats = ctx.backend.get_statistics_manager().get_statistics_entries(ctx.backend.get_auth_manager().get_nodename(ctx.request), querydata)
result={"status":"ERROR"}
if stats and len(stats) >= count:
result={"status":"OK"}
return Response(json.dumps(result), status=httplibclient.OK)