Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OpenTelemetry tracing #1355

Merged
merged 1 commit into from
Nov 13, 2023
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
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
erikoqvist marked this conversation as resolved.
Show resolved Hide resolved
tracing.init_tracing("nipap-www", nipap_config.get("tracing", "otlp_grpc_endpoint"))
erikoqvist marked this conversation as resolved.
Show resolved Hide resolved
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
27 changes: 27 additions & 0 deletions nipap-www/nipapwww/tracing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import functools

from flask import session

try:
from opentelemetry import trace
from opentelemetry.trace import INVALID_SPAN

def create_span(view):
@functools.wraps(view)
def wrapped_view(**kwargs):
current_span = trace.get_current_span()
if current_span != INVALID_SPAN:
if session.get("user") is None:
current_span.set_attribute("username", "unknown")
else:
current_span.set_attribute("username", session.get("user"))
return view(**kwargs)

return wrapped_view
except ImportError:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Black would make changes.

def create_span(view):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation is not a multiple of four

@functools.wraps(view)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

over-indented

def wrapped_view(**kwargs):
return view(**kwargs)

return wrapped_view
22 changes: 22 additions & 0 deletions nipap-www/nipapwww/xhr.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from .auth import login_required

from .tracing import create_span


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

Expand Down Expand Up @@ -65,6 +67,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 +84,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 +135,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 +164,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 +199,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 +218,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 +241,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 +282,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 +317,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 +352,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 +371,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 +391,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 +460,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 +574,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 +692,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 +772,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 +791,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 +817,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 +832,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 +877,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
16 changes: 14 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 @@ -403,9 +415,7 @@ def authenticate(self):

# 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 +505,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 +698,7 @@ def _upgrade_database(self):
pass
self._db_conn.commit()

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