diff --git a/app.py b/app.py index 0d7f0cd..b3d7a93 100644 --- a/app.py +++ b/app.py @@ -1,128 +1,136 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # # This file is part of the Keystone browser # # Copyright (c) 2017 Bryan Davis and contributors # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. # # This program 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 General Public License for # more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import flask import werkzeug.contrib.fixers from keystone_browser import glance from keystone_browser import keystone from keystone_browser import ldap from keystone_browser import nova from keystone_browser import proxies from keystone_browser import stats app = flask.Flask(__name__) app.wsgi_app = werkzeug.contrib.fixers.ProxyFix(app.wsgi_app) @app.route('/') def home(): ctx = {} try: cached = 'purge' not in flask.request.args ctx.update({ 'usage': stats.usage(cached), }) except Exception: app.logger.exception('Error collecting information for projects') return flask.render_template('home.html', **ctx) @app.route('/project/') def projects(): ctx = {} try: ctx.update({ 'projects': keystone.all_projects(), }) except Exception: app.logger.exception('Error collecting information for projects') return flask.render_template('projects.html', **ctx) @app.route('/project/') def project(name): ctx = { 'project': name, } try: users = keystone.project_users_by_role(name) admins = users['admin'] + users['projectadmin'] ctx.update({ 'project': name, 'admins': ldap.get_users_by_uid(admins), 'users': ldap.get_users_by_uid(users['user']), 'servers': nova.project_servers(name), 'flavors': nova.flavors(name), 'images': glance.images(), 'proxies': proxies.project_proxies(name), }) except Exception: app.logger.exception( 'Error collecting information for project "%s"', name) return flask.render_template('project.html', **ctx) @app.route('/user/') def user(uid): ctx = { 'uid': uid, } try: ctx.update({ 'user': ldap.get_users_by_uid([uid]), 'projects': keystone.projects_for_user(uid), }) if ctx['user']: ctx['user'] = ctx['user'][0] except Exception: app.logger.exception( 'Error collecting information for user "%s"', uid) return flask.render_template('user.html', **ctx) @app.route('/server/') def server(fqdn): name, project, tld = fqdn.split('.', 2) ctx = { 'fqdn': fqdn, 'project': project, } try: ctx.update({ 'server': nova.server(fqdn), 'flavors': nova.flavors(project), 'images': glance.images(), }) if 'user_id' in ctx['server']: user = ldap.get_users_by_uid([ctx['server']['user_id']]) if user: ctx['owner'] = user[0] except Exception: app.logger.exception( 'Error collecting information for server "%s"', fqdn) return flask.render_template('server.html', **ctx) +@app.route('/proxy/') +def all_proxies(): + ctx = { + 'proxies': proxies.all_proxies(), + } + return flask.render_template('proxies.html', **ctx) + + @app.errorhandler(404) def page_not_found(e): return flask.redirect(flask.url_for('projects')) diff --git a/keystone_browser/proxies.py b/keystone_browser/proxies.py index 5fc330e..a782a3d 100644 --- a/keystone_browser/proxies.py +++ b/keystone_browser/proxies.py @@ -1,49 +1,73 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # # This file is part of the Keystone browser # # Copyright (c) 2017 Bryan Davis and contributors # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. # # This program 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 General Public License for # more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import functools import re import requests +from . import cache from . import keystone @functools.lru_cache(maxsize=1) def url_template(): """Get the url template for accessing the proxy service.""" c = keystone.keystone_client() proxy = c.services.list(type='proxy')[0] endpoint = c.endpoints.list( service=proxy.id, interface='public', enabled=True)[0] # Secret magic! The endpoint provided by keystone is private and we can't # access it. There's an alternative public read-only endpoint on port 5669 # though. So, swap in 5669 for the port we got from keystone. return re.sub(r':[0-9]+/', ':5669/', endpoint.url) def project_proxies(project): """Get a list of proxies for a project.""" - base_url = url_template().replace('$(tenant_id)s', project) - url = '{}/mapping'.format(base_url) - req = requests.get(url, verify=False) - if req.status_code != 200: - return [] - mappings = req.json() - return mappings['routes'] + key = 'proxies:{}'.format(project) + data = cache.CACHE.load(key) + if data is None: + base_url = url_template().replace('$(tenant_id)s', project) + url = '{}/mapping'.format(base_url) + req = requests.get(url, verify=False) + if req.status_code != 200: + data = [] + else: + data = req.json()['routes'] + cache.CACHE.save(key, data, 3600) + return data + + +def all_proxies(): + """Get a list of all proxies. + + Each proxy in the list will be a dict containing project, domain, and + backends keys. + """ + key = 'proxies:all' + data = cache.CACHE.load(key) + if data is None: + data = [ + dict(project=project, **proxy) + for project in keystone.all_projects() + for proxy in project_proxies(project) + ] + cache.CACHE.save(key, data, 3600) + return data diff --git a/templates/layout.html b/templates/layout.html index 0d424f2..e4a669a 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -1,80 +1,81 @@ {% block title %}OpenStack browser{% endblock %} {% block css %}{% endblock %} {% block navbar %} {% endblock %} {% block banner %}{% endblock banner %}
{% block pre_content %} {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %} {% endfor %} {% endif %} {% endwith %} {% endblock %} {% block content %}{% endblock %} {% block post_content %}{% endblock post_content %}
{% block js %} {% endblock %} diff --git a/templates/proxies.html b/templates/proxies.html new file mode 100644 index 0000000..4f71cc3 --- /dev/null +++ b/templates/proxies.html @@ -0,0 +1,38 @@ +{% extends "layout.html" %} + +{% block title %}Proxies - {{ super() }}{% endblock %} + +{% block content %} +
+
+ + + + + + + + + + + {% for proxy in proxies|sort(attribute='project') %} + + + + + + {% endfor %} + +
ProjectDomainBackend
{{ proxy.project }}{{ proxy.domain }} + {% for backend in proxy.backends %} + {{ backend }} + {% endfor %} +
+
+
+{% endblock %}