Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/flask_state/conf/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,12 @@ class HttpMethod(Enum):
PUT = "PUT"
DELETE = "DELETE"
PATCH = "PATCH"


class HTTPStatus:
"""Http standard status code"""
OK = 200
BAD_REQUEST = 400
METHOD_NOT_ALLOWED = 405
UNAUTHORIZED = 401
INTERNAL_SERVER_ERROR = 500
20 changes: 20 additions & 0 deletions src/flask_state/controller/interceptors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- encoding: utf-8 -*-
from functools import wraps

from flask import request

from ..controller.response_methods import make_response_content
from ..exceptions import ErrorResponse
from ..exceptions.error_code import MsgCode
from ..conf.config import HTTPStatus


def json_required(f):
@wraps(f)
def wrapper(*args, **kwargs):
if request.method != 'GET' and (not request.is_json or not request.get_json(silent=True)):
return make_response_content(resp=ErrorResponse(MsgCode.JSON_FORMAT_ERROR),
http_status=HTTPStatus.BAD_REQUEST)
return f(*args, **kwargs)

return wrapper
14 changes: 7 additions & 7 deletions src/flask_state/controller/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

from flask import current_app, request

from .response_methods import make_response_content
from ..conf.config import Constant, HttpMethod
from ..exceptions import ErrorResponse
from ..controller.interceptors import json_required
from ..exceptions import ErrorResponse, FlaskStateError
from ..exceptions.error_code import MsgCode
from ..exceptions.log_msg import ErrorMsg, InfoMsg
from ..models import model_init_app
Expand All @@ -17,7 +19,6 @@
from ..utils.file_lock import Lock
from ..utils.format_conf import format_address
from ..utils.logger import DefaultLogger, logger
from .response_methods import make_response_content

ONE_MINUTE_SECONDS = 60

Expand Down Expand Up @@ -101,20 +102,19 @@ def record_timer(app, days, hours, minutes, second):

@auth_user
@auth_method
@json_required
def query_flask_state():
"""
Query the local state and redis status
:return: flask response
"""
try:
b2d = request.json
if not isinstance(b2d, dict):
logger.warning(ErrorMsg.DATA_TYPE_ERROR).get_msg(
".The target type is {}, not {}".format(dict.__name__, type(b2d).__name__)
)
return make_response_content(ErrorResponse(MsgCode.JSON_FORMAT_ERROR))
time_quantum = b2d.get("timeQuantum")
return make_response_content(resp=query_flask_state_host(time_quantum))
except FlaskStateError as e:
logger.warning(e)
return make_response_content(e, http_status=e.status_code)
except Exception as e:
logger.exception(e)
return make_response_content(ErrorResponse(MsgCode.UNKNOWN_ERROR), http_status=500)
25 changes: 25 additions & 0 deletions src/flask_state/exceptions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,28 @@ def get_level(self):

def get_msg(self, supplement=""):
return self.value.get("msg") + supplement


class FlaskStateError(Exception):
def __init__(self, **kwargs):
"""
:param int status_code: standard http status code use by FlaskState
:param str msg:
"""
self.status_code = int(kwargs.get('status_code'))
self.msg = str(kwargs.get('msg'))
self.reply_code = kwargs.get('code', self.status_code)
self.data = list()

def get_msg(self):
return self.msg

def get_code(self):
return self.reply_code

def get_data(self):
return self.data

def __repr__(self):
return '{}: ({}) {!r} {!r}'.format(self.__class__.__name__, self.status_code,
self.reply_code, self.msg, )
3 changes: 2 additions & 1 deletion src/flask_state/exceptions/error_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ class MsgCode(ErrorCode):
"msg": "Exceeding the allowed query time range",
"code": 10001,
}
REQUEST_METHOD_ERROR = {"msg": "The request method cannot be get", "code": 10002}
REQUEST_METHOD_ERROR = {"msg": "Method Not Allowed", "code": 10002}
JSON_FORMAT_ERROR = {"msg": "JSON format is required", "code": 10003}
AUTH_FAIL = {"msg": "Validation failed", "code": 10004}
UNKNOWN_ERROR = {"msg": "Unknown error", "code": 10005}
PARAMETER_ERROR = {"msg": "Parameter Error", "code": 10006}
59 changes: 28 additions & 31 deletions src/flask_state/services/host_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@

import psutil

from . import redis_conn
from ..conf.config import DAYS_SCOPE, Constant
from ..conf.config import HTTPStatus
from ..dao.host_status import (
create_host_status,
delete_thirty_days_status,
retrieve_host_status,
retrieve_host_status_yesterday,
retrieve_latest_host_status,
)
from ..exceptions import ErrorResponse, FlaskStateResponse, SuccessResponse
from ..exceptions import FlaskStateResponse, SuccessResponse, FlaskStateError
from ..exceptions.error_code import MsgCode
from ..utils.date import get_current_ms, get_current_s
from ..utils.logger import logger
from . import redis_conn

SECONDS_TO_MILLISECOND_MULTIPLE = 1000 # Second to millisecond multiple
DEFAULT_HITS_RATIO = 100 # Default hits ratio value
Expand Down Expand Up @@ -70,7 +71,7 @@ def record_flask_state_host(interval):
yesterday_keyspace_misses = yesterday_current_statistic.keyspace_misses
if yesterday_keyspace_hits is not None and yesterday_keyspace_misses is not None:
be_divided_num = (
keyspace_hits + keyspace_misses - (yesterday_keyspace_hits + yesterday_keyspace_misses)
keyspace_hits + keyspace_misses - (yesterday_keyspace_hits + yesterday_keyspace_misses)
)
delta_hits_ratio = (
float("%.2f" % ((keyspace_hits - yesterday_keyspace_hits) * PERCENTAGE / be_divided_num))
Expand Down Expand Up @@ -108,34 +109,30 @@ def query_flask_state_host(days) -> FlaskStateResponse:
:param days: the query days
:return: flask response
"""
try:
try:
if not isinstance(days, int):
days = int(days)
except Exception as t:
raise t
if days not in DAYS_SCOPE:
return ErrorResponse(MsgCode.OVERSTEP_DAYS_SCOPE)
current_status = retrieve_latest_host_status()
current_status["load_avg"] = (current_status.get("load_avg") or "").split(",")
result = retrieve_host_status(days)
result = control_result_counts(result)
arr = []
for status in result:
arr.append(
[
int(status.ts / SECONDS_TO_MILLISECOND_MULTIPLE),
status.cpu,
status.memory,
status.load_avg.split(","),
status.disk_usage,
]
)
data = {"currentStatistic": current_status, "items": arr}
return SuccessResponse(msg="Search success", data=data)
except Exception as e:
logger.exception(e)
return ErrorResponse(MsgCode.UNKNOWN_ERROR)
if str(days).isnumeric():
days = int(days)
else:
raise FlaskStateError(**MsgCode.PARAMETER_ERROR.value, status_code=HTTPStatus.BAD_REQUEST)

if days not in DAYS_SCOPE:
raise FlaskStateError(**MsgCode.OVERSTEP_DAYS_SCOPE.value, status_code=HTTPStatus.BAD_REQUEST)
current_status = retrieve_latest_host_status()
current_status["load_avg"] = (current_status.get("load_avg") or "").split(",")
result = retrieve_host_status(days)
result = control_result_counts(result)
arr = []
for status in result:
arr.append(
[
int(status.ts / SECONDS_TO_MILLISECOND_MULTIPLE),
status.cpu,
status.memory,
status.load_avg.split(","),
status.disk_usage,
]
)
data = {"currentStatistic": current_status, "items": arr}
return SuccessResponse(msg="Search success", data=data)


def control_result_counts(result) -> list:
Expand Down
8 changes: 5 additions & 3 deletions src/flask_state/utils/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from ..controller.response_methods import make_response_content
from ..exceptions import ErrorResponse
from ..exceptions.error_code import MsgCode
from ..conf.config import HTTPStatus, HttpMethod


def auth_user(func):
Expand All @@ -27,7 +28,7 @@ def get_user():
return getattr(_request_ctx_stack.top, "user", None)

if not (current_user and current_user.is_authenticated):
return make_response_content(ErrorResponse(MsgCode.AUTH_FAIL), http_status=401)
return make_response_content(ErrorResponse(MsgCode.AUTH_FAIL), http_status=HTTPStatus.UNAUTHORIZED)
return func()

return wrapper
Expand All @@ -41,8 +42,9 @@ def auth_method(func):
"""

def wrapper():
if request.method != "POST":
return make_response_content(ErrorResponse(MsgCode.REQUEST_METHOD_ERROR), http_status=401)
if request.method != HttpMethod.POST.value:
return make_response_content(ErrorResponse(MsgCode.REQUEST_METHOD_ERROR),
http_status=HTTPStatus.METHOD_NOT_ALLOWED)
return func()

return wrapper
25 changes: 19 additions & 6 deletions tests/test_services.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import contextlib

import redis

from src.flask_state.exceptions import ErrorResponse, SuccessResponse
from src.flask_state.exceptions import ErrorResponse, SuccessResponse, FlaskStateError
from src.flask_state.models import model_init_app
from src.flask_state.services import host_status, redis_conn


@contextlib.contextmanager
def raises(custom_exception):
try:
yield
except custom_exception as e:
assert True
else:
assert False


def test_redis(app):
"""
Test whether the custom redis class method can be used normally
Expand Down Expand Up @@ -42,7 +54,7 @@ def test_query_flask_state_host(app):
"""
model_init_app(app)
test_right_day = [1, 3, 7, 30]
test_error_day = [5, 10, 31, 100]
test_error_day = [100, 'hello']
with app.app_context():
for day in test_right_day:
response_content = host_status.query_flask_state_host(day)
Expand All @@ -53,7 +65,8 @@ def test_query_flask_state_host(app):
assert isinstance(response_content, SuccessResponse)

for day in test_error_day:
response_content = host_status.query_flask_state_host(day)
assert isinstance(response_content.get_code(), int)
assert isinstance(response_content.get_msg(), str)
assert isinstance(response_content, ErrorResponse)
with raises(FlaskStateError):
response_content = host_status.query_flask_state_host(day)
assert isinstance(response_content.get_code(), int)
assert isinstance(response_content.get_msg(), str)
assert isinstance(response_content, ErrorResponse)
4 changes: 2 additions & 2 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ def test_method():
get_response = c.get("/test_method")

ok = 200
error = 401
error_request_method = "The request method cannot be get"
error = 405
error_request_method = 'Method Not Allowed'
assert ok == post_response.status_code
assert error == get_response.status_code
assert error_request_method == json.loads(str(get_response.data, "utf-8")).get("msg")
Expand Down