Skip to content

Commit

Permalink
Add OpenTelemetry tracing
Browse files Browse the repository at this point in the history
  • Loading branch information
erikoqvist committed Nov 1, 2023
1 parent 9f0501a commit 44c7705
Show file tree
Hide file tree
Showing 13 changed files with 303 additions and 33 deletions.
18 changes: 17 additions & 1 deletion nipap-cli/nipap
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import nipap_cli
import nipap_cli.nipap_cli
from nipap_cli.command import Command, CommandError
from pynipap import NipapError

import tracing

# early close of stdout to avoid broken pipe, see #464
# If our output is being piped and the receiver of the pipe is killed off before
Expand Down Expand Up @@ -88,6 +88,22 @@ if __name__ == '__main__':
cfg.read(userrcfile)
nipap_cli.nipap_cli.cfg = cfg

if cfg.has_section("tracing"):
try:
use_grpc = True
try:
otlp_endpoint = cfg.get("tracing", "otlp_grpc_endpoint")
except configparser.NoOptionError:
# Send trace via nipapd
use_grpc = False
use_ssl = cfg.getboolean("global", "use_ssl")
otlp_endpoint = "https://" if use_ssl else "http://" + (cfg.get("global", "hostname") +
":" + cfg.get("global", "port") +
"/v1/traces/")
tracing.init_tracing("nipap-cli", otlp_endpoint, use_grpc)
except KeyError:
pass

# setup our configuration
nipap_cli.nipap_cli.setup_connection()

Expand Down
4 changes: 4 additions & 0 deletions nipap-cli/nipaprc
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,7 @@ default_list_vrf_rt = all
#
# prefix_list_columns = vrf_rt,prefix
#
# Enable OpenTelemetry tracing by uncommenting section.
# [tracing]
# Specify OTLP GRPC endpoint. If no endpoint is specified traces will be sent via nipapd to OpenTelemetry Collector
# otlp_grpc_endpoint = http://127.0.0.1:4317
12 changes: 12 additions & 0 deletions nipap-www/nipapwww/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ def create_app(test_config=None):
# configure pynipap
pynipap.xmlrpc_uri = app.config["XMLRPC_URI"]

# configure tracing
if nipap_config.has_section("tracing"):
try:
import tracing
from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware
tracing.init_tracing("nipap-www", nipap_config.get("tracing", "otlp_grpc_endpoint"))
app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app)
except KeyError:
pass
except ImportError:
pass

# Set up blueprints
from . import auth, ng, prefix, static, version, xhr
app.register_blueprint(auth.bp)
Expand Down
23 changes: 23 additions & 0 deletions nipap-www/nipapwww/xhr.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import functools
import json

from flask import Blueprint, request, session
Expand All @@ -6,6 +7,8 @@

from .auth import login_required

from .tracing import create_span


bp = Blueprint('xhr', __name__, url_prefix='/xhr')

Expand Down Expand Up @@ -65,6 +68,7 @@ def extract_pool_attr(req):
# TODO: Is this used any more?
@bp.route('/list_vrf')
@login_required
@create_span
def list_vrf():
""" List VRFs and return JSON encoded result.
"""
Expand All @@ -81,6 +85,7 @@ def list_vrf():

@bp.route('/smart_search_vrf', methods=('GET', 'POST'))
@login_required
@create_span
def smart_search_vrf():
""" Perform a smart VRF search.
Expand Down Expand Up @@ -131,6 +136,7 @@ def smart_search_vrf():

@bp.route('/add_vrf', methods=('GET', 'POST'))
@login_required
@create_span
def add_vrf():
""" Add a new VRF to NIPAP and return its data.
"""
Expand Down Expand Up @@ -159,6 +165,7 @@ def add_vrf():

@bp.route('/edit_vrf/<id>', methods=('GET', 'POST'))
@login_required
@create_span
def edit_vrf(id):
""" Edit a VRF.
"""
Expand Down Expand Up @@ -193,6 +200,7 @@ def edit_vrf(id):

@bp.route('/remove_vrf/<id>', methods=('GET', 'POST'))
@login_required
@create_span
def remove_vrf(id):
""" Remove a VRF.
"""
Expand All @@ -211,6 +219,7 @@ def remove_vrf(id):

@bp.route('/list_pool', methods=('GET', 'POST'))
@login_required
@create_span
def list_pool():
""" List pools and return JSON encoded result.
"""
Expand All @@ -233,6 +242,7 @@ def list_pool():

@bp.route('/smart_search_pool')
@login_required
@create_span
def smart_search_pool():
""" Perform a smart pool search.
Expand Down Expand Up @@ -273,6 +283,7 @@ def smart_search_pool():

@bp.route('/add_pool', methods=('GET', 'POST'))
@login_required
@create_span
def add_pool():
""" Add a pool.
"""
Expand Down Expand Up @@ -307,6 +318,7 @@ def add_pool():

@bp.route('/edit_pool/<id>', methods=('GET', 'POST'))
@login_required
@create_span
def edit_pool(id):
""" Edit a pool.
"""
Expand Down Expand Up @@ -341,6 +353,7 @@ def edit_pool(id):

@bp.route('/remove_pool/<id>', methods=('GET', 'POST'))
@login_required
@create_span
def remove_pool(id):
""" Remove a pool.
"""
Expand All @@ -359,6 +372,7 @@ def remove_pool(id):

@bp.route('/list_prefix', methods=('GET', 'POST'))
@login_required
@create_span
def list_prefix():
""" List prefixes and return JSON encoded result.
"""
Expand All @@ -378,6 +392,7 @@ def list_prefix():

@bp.route('/search_prefix', methods=('GET', 'POST'))
@login_required
@create_span
def search_prefix():
""" Search prefixes. Does not yet incorporate all the functions of the
search_prefix API function due to difficulties with transferring
Expand Down Expand Up @@ -446,6 +461,7 @@ def search_prefix():

@bp.route('/smart_search_prefix', methods=('GET', 'POST'))
@login_required
@create_span
def smart_search_prefix():
""" Perform a smart search.
Expand Down Expand Up @@ -559,6 +575,7 @@ def smart_search_prefix():

@bp.route('/add_prefix', methods=('GET', 'POST'))
@login_required
@create_span
def add_prefix():
""" Add prefix according to the specification.
Expand Down Expand Up @@ -676,6 +693,7 @@ def add_prefix():

@bp.route('/edit_prefix/<id>', methods=('GET', 'POST'))
@login_required
@create_span
def edit_prefix(id):
""" Edit a prefix.
"""
Expand Down Expand Up @@ -755,6 +773,7 @@ def edit_prefix(id):

@bp.route('/remove_prefix/<id>', methods=('GET', 'POST'))
@login_required
@create_span
def remove_prefix(id):
""" Remove a prefix.
"""
Expand All @@ -773,6 +792,7 @@ def remove_prefix(id):

@bp.route('/add_current_vrf', methods=('GET', 'POST'))
@login_required
@create_span
def add_current_vrf():
""" Add VRF to filter list session variable
"""
Expand All @@ -798,6 +818,7 @@ def add_current_vrf():

@bp.route('/del_current_vrf', methods=('GET', 'POST'))
@login_required
@create_span
def del_current_vrf():
""" Remove VRF to filter list session variable
"""
Expand All @@ -812,6 +833,7 @@ def del_current_vrf():

@bp.route('/get_current_vrfs')
@login_required
@create_span
def get_current_vrfs():
""" Return VRF filter list from session variable
Expand Down Expand Up @@ -856,6 +878,7 @@ def get_current_vrfs():

@bp.route('/list_tags')
@login_required
@create_span
def list_tags(self):
""" List Tags and return JSON encoded result.
"""
Expand Down
6 changes: 6 additions & 0 deletions nipap/nipap.conf.dist
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,9 @@ secret_key = {{WWW_SECRET_KEY}}
# the web interface, it might contain hints about what credentials to use or
# just greet the user.
#welcome_message = Welcome to NIPAP at COMPANY. Please login using your XYZ credentials.
# Enable OpenTelemetry tracing by uncommenting section.
# [tracing]
# Specify OTLP GRPC endpoint. Used to send traces to OpenTelemetry Collector
# otlp_grpc_endpoint=http://opentelemetry-collector:4317
# Specify OTLP HTTP endpoint. Used when proxying traces to OpenTelemetry-Collector from nipap-cli
# otlp_http_endpoint=http://opentelemetry-collector:4318/v1/traces
17 changes: 15 additions & 2 deletions nipap/nipap/authlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
import random
import requests

from .tracing import create_span_authenticate

try:
import jwt
except ImportError:
Expand Down Expand Up @@ -336,6 +338,16 @@ def __init__(self, name, jwt_token, authoritative_source,
self._logger.error('Unable to load Python jwt module, please verify it is installed')
raise AuthError('Unable to authenticate')

# Decode token
try:
payload = jwt.decode(
self._jwt_token, options={"verify_signature": False})
self.username = payload.get('sub')
self.full_name = payload.get('name', payload.get('sub'))
except jwt.exceptions.DecodeError:
raise AuthError('Failed to decode jwt token')

@create_span_authenticate
def authenticate(self):
""" Verify authentication.
Expand Down Expand Up @@ -397,15 +409,14 @@ def authenticate(self):
self._authenticated = False
return self._authenticated
except:
raise
self._logger.debug('could not authenticate')
self._authenticated = False
return self._authenticated

# auth succeeded
if self._authenticated:
self.username = payload.get('sub')
self.authenticated_as = payload.get('sub')
self.full_name = payload.get('name', payload.get('sub'))
self._logger.debug('successfully authenticated as %s, username' % self.authenticated_as)
self.trusted = False

Expand Down Expand Up @@ -495,6 +506,7 @@ def __init__(self, name, username, password, authoritative_source, auth_options=
self._logger.exception(exc)
raise AuthError('Unable to establish secure connection to ldap server')

@create_span_authenticate
def authenticate(self):
""" Verify authentication.
Expand Down Expand Up @@ -687,6 +699,7 @@ def _upgrade_database(self):
pass
self._db_conn.commit()

@create_span_authenticate
def authenticate(self):
""" Verify authentication.
Expand Down
Loading

0 comments on commit 44c7705

Please sign in to comment.