fix references to gds

This commit is contained in:
Kenneth Kehl
2023-08-17 09:01:53 -07:00
parent e4a37c84ca
commit 1765dba476
9 changed files with 15 additions and 104 deletions

View File

@@ -17,8 +17,6 @@ from flask import (
from flask_marshmallow import Marshmallow from flask_marshmallow import Marshmallow
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy as _SQLAlchemy from flask_sqlalchemy import SQLAlchemy as _SQLAlchemy
from gds_metrics import GDSMetrics
from gds_metrics.metrics import Gauge, Histogram
from notifications_utils import logging, request_helper from notifications_utils import logging, request_helper
from notifications_utils.celery import NotifyCelery from notifications_utils.celery import NotifyCelery
from notifications_utils.clients.encryption.encryption_client import Encryption from notifications_utils.clients.encryption.encryption_client import Encryption
@@ -61,18 +59,13 @@ encryption = Encryption()
zendesk_client = ZendeskClient() zendesk_client = ZendeskClient()
redis_store = RedisClient() redis_store = RedisClient()
document_download_client = DocumentDownloadClient() document_download_client = DocumentDownloadClient()
metrics = GDSMetrics()
notification_provider_clients = NotificationProviderClients() notification_provider_clients = NotificationProviderClients()
api_user = LocalProxy(lambda: g.api_user) api_user = LocalProxy(lambda: g.api_user)
authenticated_service = LocalProxy(lambda: g.authenticated_service) authenticated_service = LocalProxy(lambda: g.authenticated_service)
CONCURRENT_REQUESTS = Gauge(
'concurrent_web_request_count',
'How many concurrent requests are currently being served',
)
def create_app(application): def create_app(application):
from app.config import configs from app.config import configs
@@ -84,8 +77,6 @@ def create_app(application):
application.config['NOTIFY_APP_NAME'] = application.name application.config['NOTIFY_APP_NAME'] = application.name
init_app(application) init_app(application)
# Metrics intentionally high up to give the most accurate timing and reliability that the metric is recorded
metrics.init_app(application)
request_helper.init_app(application) request_helper.init_app(application)
db.init_app(application) db.init_app(application)
migrate.init_app(application, db=db) migrate.init_app(application, db=db)
@@ -280,14 +271,12 @@ def init_app(app):
@app.before_request @app.before_request
def record_request_details(): def record_request_details():
CONCURRENT_REQUESTS.inc()
g.start = monotonic() g.start = monotonic()
g.endpoint = request.endpoint g.endpoint = request.endpoint
@app.after_request @app.after_request
def after_request(response): def after_request(response):
CONCURRENT_REQUESTS.dec()
response.headers.add('X-Content-Type-Options', 'nosniff') response.headers.add('X-Content-Type-Options', 'nosniff')
return response return response
@@ -339,39 +328,19 @@ def create_random_identifier():
# TODO maintainability what is the purpose of this? Debugging? # TODO maintainability what is the purpose of this? Debugging?
def setup_sqlalchemy_events(app): def setup_sqlalchemy_events(app):
TOTAL_DB_CONNECTIONS = Gauge(
'db_connection_total_connected',
'How many db connections are currently held (potentially idle) by the server',
)
TOTAL_CHECKED_OUT_DB_CONNECTIONS = Gauge(
'db_connection_total_checked_out',
'How many db connections are currently checked out by web requests',
)
DB_CONNECTION_OPEN_DURATION_SECONDS = Histogram(
'db_connection_open_duration_seconds',
'How long db connections are held open for in seconds',
['method', 'host', 'path']
)
# need this or db.engine isn't accessible # need this or db.engine isn't accessible
with app.app_context(): with app.app_context():
@event.listens_for(db.engine, 'connect') @event.listens_for(db.engine, 'connect')
def connect(dbapi_connection, connection_record): # noqa def connect(dbapi_connection, connection_record): # noqa
# connection first opened with db pass
TOTAL_DB_CONNECTIONS.inc()
@event.listens_for(db.engine, 'close') @event.listens_for(db.engine, 'close')
def close(dbapi_connection, connection_record): # noqa def close(dbapi_connection, connection_record): # noqa
# connection closed (probably only happens with overflow connections) pass
TOTAL_DB_CONNECTIONS.dec()
@event.listens_for(db.engine, 'checkout') @event.listens_for(db.engine, 'checkout')
def checkout(dbapi_connection, connection_record, connection_proxy): # noqa def checkout(dbapi_connection, connection_record, connection_proxy): # noqa
try: try:
# connection given to a web worker
TOTAL_CHECKED_OUT_DB_CONNECTIONS.inc()
# this will overwrite any previous checkout_at timestamp # this will overwrite any previous checkout_at timestamp
connection_record.info['checkout_at'] = time.monotonic() connection_record.info['checkout_at'] = time.monotonic()
@@ -407,17 +376,4 @@ def setup_sqlalchemy_events(app):
@event.listens_for(db.engine, 'checkin') @event.listens_for(db.engine, 'checkin')
def checkin(dbapi_connection, connection_record): # noqa def checkin(dbapi_connection, connection_record): # noqa
try: pass
# connection returned by a web worker
TOTAL_CHECKED_OUT_DB_CONNECTIONS.dec()
# duration that connection was held by a single web request
duration = time.monotonic() - connection_record.info['checkout_at']
DB_CONNECTION_OPEN_DURATION_SECONDS.labels(
connection_record.info['request_data']['method'],
connection_record.info['request_data']['host'],
connection_record.info['request_data']['url_rule']
).observe(duration)
except Exception:
current_app.logger.exception("Exception caught for checkin event.")

View File

@@ -1,7 +1,6 @@
import uuid import uuid
from flask import current_app, g, request from flask import current_app, g, request
from gds_metrics import Histogram
from notifications_python_client.authentication import ( from notifications_python_client.authentication import (
decode_jwt_token, decode_jwt_token,
get_token_issuer, get_token_issuer,
@@ -24,11 +23,6 @@ TOKEN_MESSAGE_ONE = "Invalid token: make sure your API token matches the example
TOKEN_MESSAGE_TWO = "at https://docs.notifications.service.gov.uk/rest-api.html#authorisation-header" # nosec B105 TOKEN_MESSAGE_TWO = "at https://docs.notifications.service.gov.uk/rest-api.html#authorisation-header" # nosec B105
GENERAL_TOKEN_ERROR_MESSAGE = TOKEN_MESSAGE_ONE + TOKEN_MESSAGE_TWO GENERAL_TOKEN_ERROR_MESSAGE = TOKEN_MESSAGE_ONE + TOKEN_MESSAGE_TWO
AUTH_DB_CONNECTION_DURATION_SECONDS = Histogram(
'auth_db_connection_duration_seconds',
'Time taken to get DB connection and fetch service from database',
)
class AuthError(Exception): class AuthError(Exception):
def __init__(self, message, code, service_id=None, api_key_id=None): def __init__(self, message, code, service_id=None, api_key_id=None):
@@ -102,8 +96,7 @@ def requires_auth():
raise AuthError("Invalid token: service id is not the right data type", 403) raise AuthError("Invalid token: service id is not the right data type", 403)
try: try:
with AUTH_DB_CONNECTION_DURATION_SECONDS.time(): service = SerialisedService.from_id(service_id)
service = SerialisedService.from_id(service_id)
except NoResultFound: except NoResultFound:
raise AuthError("Invalid token: service not found", 403) raise AuthError("Invalid token: service not found", 403)

View File

@@ -2,7 +2,6 @@ import uuid
from datetime import datetime from datetime import datetime
from flask import current_app from flask import current_app
from gds_metrics import Histogram
from notifications_utils.clients import redis from notifications_utils.clients import redis
from notifications_utils.recipients import ( from notifications_utils.recipients import (
format_email_address, format_email_address,
@@ -30,11 +29,6 @@ from app.models import (
) )
from app.v2.errors import BadRequestError from app.v2.errors import BadRequestError
REDIS_GET_AND_INCR_DAILY_LIMIT_DURATION_SECONDS = Histogram(
'redis_get_and_incr_daily_limit_duration_seconds',
'Time taken to get and possibly incremement the daily limit cache key',
)
def create_content_for_notification(template, personalisation): def create_content_for_notification(template, personalisation):
if template.template_type == EMAIL_TYPE: if template.template_type == EMAIL_TYPE:

View File

@@ -1,5 +1,4 @@
from flask import Blueprint, current_app, json, jsonify, request from flask import Blueprint, current_app, json, jsonify, request
from gds_metrics.metrics import Counter
from notifications_utils.recipients import try_validate_and_format_phone_number from notifications_utils.recipients import try_validate_and_format_phone_number
from app.celery import tasks from app.celery import tasks
@@ -14,13 +13,6 @@ receive_notifications_blueprint = Blueprint('receive_notifications', __name__)
register_errors(receive_notifications_blueprint) register_errors(receive_notifications_blueprint)
INBOUND_SMS_COUNTER = Counter(
'inbound_sms',
'Total number of inbound SMS received',
['provider']
)
@receive_notifications_blueprint.route('/notifications/sms/receive/sns', methods=['POST']) @receive_notifications_blueprint.route('/notifications/sms/receive/sns', methods=['POST'])
def receive_sns_sms(): def receive_sns_sms():
""" """
@@ -64,8 +56,6 @@ def receive_sns_sms():
result="success", message="SMS-SNS callback succeeded" result="success", message="SMS-SNS callback succeeded"
), 200 ), 200
INBOUND_SMS_COUNTER.labels("sns").inc()
content = message.get("messageBody") content = message.get("messageBody")
from_number = message.get('originationNumber') from_number = message.get('originationNumber')
provider_ref = message.get('inboundMessageId') provider_ref = message.get('inboundMessageId')

View File

@@ -1,5 +1,4 @@
from flask import current_app from flask import current_app
from gds_metrics.metrics import Histogram
from notifications_utils import SMS_CHAR_COUNT_LIMIT from notifications_utils import SMS_CHAR_COUNT_LIMIT
from notifications_utils.clients.redis import ( from notifications_utils.clients.redis import (
daily_total_cache_key, daily_total_cache_key,
@@ -31,21 +30,15 @@ from app.service.utils import service_allowed_to_send_to
from app.utils import get_public_notify_type_text from app.utils import get_public_notify_type_text
from app.v2.errors import BadRequestError, RateLimitError, TotalRequestsError from app.v2.errors import BadRequestError, RateLimitError, TotalRequestsError
REDIS_EXCEEDED_RATE_LIMIT_DURATION_SECONDS = Histogram(
'redis_exceeded_rate_limit_duration_seconds',
'Time taken to check rate limit',
)
def check_service_over_api_rate_limit(service, api_key): def check_service_over_api_rate_limit(service, api_key):
if current_app.config['API_RATE_LIMIT_ENABLED'] and current_app.config['REDIS_ENABLED']: if current_app.config['API_RATE_LIMIT_ENABLED'] and current_app.config['REDIS_ENABLED']:
cache_key = rate_limit_cache_key(service.id, api_key.key_type) cache_key = rate_limit_cache_key(service.id, api_key.key_type)
rate_limit = service.rate_limit rate_limit = service.rate_limit
interval = 60 interval = 60
with REDIS_EXCEEDED_RATE_LIMIT_DURATION_SECONDS.time(): if redis_store.exceeded_rate_limit(cache_key, rate_limit, interval):
if redis_store.exceeded_rate_limit(cache_key, rate_limit, interval): current_app.logger.info("service {} has been rate limited for throughput".format(service.id))
current_app.logger.info("service {} has been rate limited for throughput".format(service.id)) raise RateLimitError(rate_limit, interval, api_key.key_type)
raise RateLimitError(rate_limit, interval, api_key.key_type)
def check_application_over_retention_limit(key_type, service): def check_application_over_retention_limit(key_type, service):

View File

@@ -4,7 +4,6 @@ from datetime import datetime
import botocore import botocore
from flask import abort, current_app, jsonify, request from flask import abort, current_app, jsonify, request
from gds_metrics import Histogram
from notifications_utils.recipients import try_validate_and_format_phone_number from notifications_utils.recipients import try_validate_and_format_phone_number
from app import ( from app import (
@@ -53,23 +52,17 @@ from app.v2.notifications.notification_schemas import (
) )
from app.v2.utils import get_valid_json from app.v2.utils import get_valid_json
POST_NOTIFICATION_JSON_PARSE_DURATION_SECONDS = Histogram(
'post_notification_json_parse_duration_seconds',
'Time taken to parse and validate post request json',
)
@v2_notification_blueprint.route('/<notification_type>', methods=['POST']) @v2_notification_blueprint.route('/<notification_type>', methods=['POST'])
def post_notification(notification_type): def post_notification(notification_type):
with POST_NOTIFICATION_JSON_PARSE_DURATION_SECONDS.time(): request_json = get_valid_json()
request_json = get_valid_json()
if notification_type == EMAIL_TYPE: if notification_type == EMAIL_TYPE:
form = validate(request_json, post_email_request) form = validate(request_json, post_email_request)
elif notification_type == SMS_TYPE: elif notification_type == SMS_TYPE:
form = validate(request_json, post_sms_request) form = validate(request_json, post_sms_request)
else: else:
abort(404) abort(404)
check_service_has_permission(notification_type, authenticated_service.permissions) check_service_has_permission(notification_type, authenticated_service.permissions)

View File

@@ -5,7 +5,6 @@ import gunicorn
import eventlet import eventlet
import socket import socket
from gds_metrics.gunicorn import child_exit # noqa
workers = 4 workers = 4
worker_class = "eventlet" worker_class = "eventlet"

View File

@@ -2,11 +2,6 @@
from flask import Flask from flask import Flask
# import prometheus before any other code. If gds_metrics is imported first it will write a prometheus file to disk
# that will never be read from (since we don't have prometheus celery stats). If prometheus is imported first,
# prometheus will simply store the metrics in memory
import prometheus_client # noqa
# notify_celery is referenced from manifest_delivery_base.yml, and cannot be removed # notify_celery is referenced from manifest_delivery_base.yml, and cannot be removed
from app import notify_celery, create_app # noqa from app import notify_celery, create_app # noqa

View File

@@ -11,6 +11,4 @@ def test_all_routes_have_authentication(client):
# The static route is always available by default for a Flask app to serve anything in the static folder. # The static route is always available by default for a Flask app to serve anything in the static folder.
routes_blueprint_names.remove('static') routes_blueprint_names.remove('static')
# The metrics route is not protected by auth as it's available to be scraped by Prometheus
routes_blueprint_names.remove('metrics')
assert sorted(blueprint_names) == sorted(routes_blueprint_names) assert sorted(blueprint_names) == sorted(routes_blueprint_names)