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 %}