Files
kops-arm64/app.py
Christian Lohmann a267272796 added sorting feature
2016-12-23 12:00:40 +01:00

263 lines
9.1 KiB
Python
Executable File

#!/usr/bin/env python3
import gevent.monkey
gevent.monkey.patch_all()
import flask
import functools
import gevent.wsgi
import json
import logging
import os
import requests
import datetime
import tokens
from flask import Flask, redirect
from flask_oauthlib.client import OAuth, OAuthRemoteApp
from urllib.parse import urljoin
DEFAULT_CLUSTERS = 'http://localhost:8001/'
CREDENTIALS_DIR = os.getenv('CREDENTIALS_DIR', '')
AUTHORIZE_URL = os.getenv('AUTHORIZE_URL')
APP_URL = os.getenv('APP_URL')
MOCK = os.getenv('MOCK', '').lower() == 'true'
app = Flask(__name__)
app.debug = os.getenv('DEBUG', '').lower() == 'true'
app.secret_key = os.getenv('SECRET_KEY', 'development')
oauth = OAuth(app)
class OAuthRemoteAppWithRefresh(OAuthRemoteApp):
'''Same as flask_oauthlib.client.OAuthRemoteApp, but always loads client credentials from file.'''
def __init__(self, oauth, name, **kwargs):
# constructor expects some values, so make it happy..
kwargs['consumer_key'] = 'not-needed-here'
kwargs['consumer_secret'] = 'not-needed-here'
OAuthRemoteApp.__init__(self, oauth, name, **kwargs)
def refresh_credentials(self):
with open(os.path.join(CREDENTIALS_DIR, 'authcode-client-id')) as fd:
self._consumer_key = fd.read().strip()
with open(os.path.join(CREDENTIALS_DIR, 'authcode-client-secret')) as fd:
self._consumer_secret = fd.read().strip()
@property
def consumer_key(self):
self.refresh_credentials()
return self._consumer_key
@property
def consumer_secrect(self):
self.refresh_credentials()
return self._consumer_secret
def authorize(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
if AUTHORIZE_URL and 'auth_token' not in flask.session:
return redirect(urljoin(APP_URL, '/login'))
return f(*args, **kwargs)
return wrapper
auth = OAuthRemoteAppWithRefresh(
oauth,
'auth',
request_token_url=None,
access_token_method='POST',
access_token_url=os.getenv('ACCESS_TOKEN_URL'),
authorize_url=AUTHORIZE_URL
)
oauth.remote_apps['auth'] = auth
session = requests.Session()
tokens.configure(from_file_only=True)
tokens.manage('read-only')
@app.route('/health')
def health():
return 'OK'
@app.route('/')
@authorize
def index():
app_js = None
for entry in os.listdir('static/build'):
if entry.startswith('app'):
app_js = entry
break
return flask.render_template('index.html', app_js=app_js)
def hash_int(x: int):
x = ((x >> 16) ^ x) * 0x45d9f3b
x = ((x >> 16) ^ x) * 0x45d9f3b
x = (x >> 16) ^ x
return x
def generate_mock_cluster_data(index: int):
'''Generate deterministic (no randomness!) mock data'''
nodes = []
pod_phases = ['Pending', 'Running', 'Running']
for i in range(10):
labels = {}
if i < 2:
labels['master'] = 'true'
pods = []
for j in range(hash_int((index + 1) * (i + 1)) % 32):
phase = pod_phases[hash_int((index + 1) * (i + 1) * (j + 1)) % len(pod_phases)]
containers = []
for k in range(1):
containers.append({'name': 'myapp', 'image': 'foo/bar/{}'.format(j),
'resources': {'requests': {'cpu': '100m', 'memory': '100Mi'}}})
status = {'phase': phase}
if phase == 'Running':
if j % 13 == 0:
status['containerStatuses'] = [
{'ready': False, 'state': {'waiting': {'reason': 'CrashLoopBackOff'}}}]
elif j % 7 == 0:
status['containerStatuses'] = [{'ready': True, 'state': {'running': {}}, 'restartCount': 3}]
pods.append(
{'name': 'my-pod-{}'.format(j), 'namespace': 'kube-system' if j < 3 else 'default', 'labels': labels,
'status': status, 'containers': containers})
nodes.append({'name': 'node-{}'.format(i), 'labels': labels,
'status': {'capacity': {'cpu': '4', 'memory': '32Gi', 'pods': '110'}}, 'pods': pods})
unassigned_pods = []
return {
'api_server_url': 'https://kube-{}.example.org'.format(index),
'nodes': nodes,
'unassigned_pods': unassigned_pods
}
def get_mock_clusters():
clusters = []
for i in range(3):
clusters.append(generate_mock_cluster_data(i))
return clusters
def get_kubernetes_clusters():
clusters = []
for api_server_url in (os.getenv('CLUSTERS') or DEFAULT_CLUSTERS).split(','):
if 'localhost' not in api_server_url:
# TODO: hacky way of detecting whether we need a token or not
session.headers['Authorization'] = 'Bearer {}'.format(tokens.get('read-only'))
response = session.get(urljoin(api_server_url, '/api/v1/nodes'), timeout=5)
response.raise_for_status()
nodes = []
nodes_by_name = {}
pods_by_namespace_name = {}
unassigned_pods = []
for node in response.json()['items']:
obj = {'name': node['metadata']['name'], 'labels': node['metadata']['labels'], 'status': node['status'],
'pods': []}
nodes.append(obj)
nodes_by_name[obj['name']] = obj
response = session.get(urljoin(api_server_url, '/api/v1/pods'), timeout=5)
response.raise_for_status()
for pod in response.json()['items']:
obj = {'name': pod['metadata']['name'],
'namespace': pod['metadata']['namespace'],
'labels': pod['metadata'].get('labels', {}),
'status': pod['status'],
'startTime': pod['status']['startTime'] if 'startTime' in pod['status'] else '',
'containers': []
}
if 'deletionTimestamp' in pod['metadata']:
obj['deleted'] = datetime.datetime.strptime(pod['metadata']['deletionTimestamp'],
'%Y-%m-%dT%H:%M:%SZ').replace(
tzinfo=datetime.timezone.utc).timestamp()
for cont in pod['spec']['containers']:
obj['containers'].append({'name': cont['name'], 'image': cont['image'], 'resources': cont['resources']})
pods_by_namespace_name[(obj['namespace'], obj['name'])] = obj
if 'nodeName' in pod['spec'] and pod['spec']['nodeName'] in nodes_by_name:
nodes_by_name[pod['spec']['nodeName']]['pods'].append(obj)
else:
unassigned_pods.append(obj)
try:
response = session.get(urljoin(api_server_url, '/api/v1/namespaces/kube-system/services/heapster/proxy/apis/metrics/v1alpha1/nodes'), timeout=5)
response.raise_for_status()
for metrics in response.json()['items']:
nodes_by_name[metrics['metadata']['name']]['usage'] = metrics['usage']
except:
logging.exception('Failed to get metrics')
try:
response = session.get(urljoin(api_server_url,
'/api/v1/namespaces/kube-system/services/heapster/proxy/apis/metrics/v1alpha1/pods'),
timeout=5)
response.raise_for_status()
for metrics in response.json()['items']:
pod = pods_by_namespace_name.get((metrics['metadata']['namespace'], metrics['metadata']['name']))
if pod:
for container in pod['containers']:
for container_metrics in metrics['containers']:
if container['name'] == container_metrics['name']:
container['resources']['usage'] = container_metrics['usage']
except:
logging.exception('Failed to get metrics')
clusters.append({'api_server_url': api_server_url, 'nodes': nodes, 'unassigned_pods': unassigned_pods})
return clusters
@app.route('/kubernetes-clusters')
@authorize
def get_clusters():
if MOCK:
clusters = get_mock_clusters()
else:
clusters = get_kubernetes_clusters()
return json.dumps({'kubernetes_clusters': clusters}, separators=(',', ':'))
@app.route('/login')
def login():
redirect_uri = urljoin(APP_URL, '/login/authorized')
return auth.authorize(callback=redirect_uri)
@app.route('/logout')
def logout():
flask.session.pop('auth_token', None)
return redirect(urljoin(APP_URL, '/'))
@app.route('/login/authorized')
def authorized():
resp = auth.authorized_response()
if resp is None:
return 'Access denied: reason=%s error=%s' % (
flask.request.args['error'],
flask.request.args['error_description']
)
if not isinstance(resp, dict):
return 'Invalid auth response'
flask.session['auth_token'] = (resp['access_token'], '')
return redirect(urljoin(APP_URL, '/'))
@auth.tokengetter
def get_auth_oauth_token():
return flask.session.get('auth_token')
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
port = 8080
http_server = gevent.wsgi.WSGIServer(('0.0.0.0', port), app)
logging.info('Listening on {}..'.format(port))
http_server.serve_forever()