Merge pull request #53 from hjacobs/mock

Mock
This commit is contained in:
Henning Jacobs
2016-12-22 12:10:10 +01:00
committed by GitHub
7 changed files with 85 additions and 16 deletions

View File

@@ -61,6 +61,18 @@ Afterwards you can open "kube-ops-view" via the kubectl proxy:
Now direct your browser to http://localhost:8001/api/v1/proxy/namespaces/default/services/kube-ops-view/ Now direct your browser to http://localhost:8001/api/v1/proxy/namespaces/default/services/kube-ops-view/
Mock Mode
=========
You can start the app in "mock mode" to see all UI features without running any Kubernetes cluster:
.. code-block:: bash
$ pip3 install -r requirements.txt
$ (cd app && npm start &)
$ MOCK=true ./app.py
Configuration Configuration
============= =============
@@ -71,7 +83,9 @@ The following environment variables are supported:
``CREDENTIALS_DIR`` ``CREDENTIALS_DIR``
Directory to read (OAuth) credentials from --- these credentials are only used for non-localhost cluster URLs. Directory to read (OAuth) credentials from --- these credentials are only used for non-localhost cluster URLs.
``DEBUG`` ``DEBUG``
Set to a non-empty value for local development to reload code changes. Set to "true" for local development to reload code changes.
``MOCK``
Set to "true" to mock Kubernetes cluster data.
Supported Browsers Supported Browsers

59
app.py
View File

@@ -22,9 +22,10 @@ DEFAULT_CLUSTERS = 'http://localhost:8001/'
CREDENTIALS_DIR = os.getenv('CREDENTIALS_DIR', '') CREDENTIALS_DIR = os.getenv('CREDENTIALS_DIR', '')
AUTHORIZE_URL = os.getenv('AUTHORIZE_URL') AUTHORIZE_URL = os.getenv('AUTHORIZE_URL')
APP_URL = os.getenv('APP_URL') APP_URL = os.getenv('APP_URL')
MOCK = os.getenv('MOCK', '').lower() == 'true'
app = Flask(__name__) app = Flask(__name__)
app.debug = os.getenv('DEBUG') == 'true' app.debug = os.getenv('DEBUG', '').lower() == 'true'
app.secret_key = os.getenv('SECRET_KEY', 'development') app.secret_key = os.getenv('SECRET_KEY', 'development')
oauth = OAuth(app) oauth = OAuth(app)
@@ -98,9 +99,49 @@ def index():
return flask.render_template('index.html', app_js=app_js) return flask.render_template('index.html', app_js=app_js)
@app.route('/kubernetes-clusters') def hash_int(x: int):
@authorize x = ((x >> 16) ^ x) * 0x45d9f3b
def get_clusters(): 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 % 10 == 0:
status['containerStatuses'] = [{'ready': False, 'state': {'waiting': {'reason': 'CrashLoopBackOff'}}}]
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 = [] clusters = []
for api_server_url in (os.getenv('CLUSTERS') or DEFAULT_CLUSTERS).split(','): for api_server_url in (os.getenv('CLUSTERS') or DEFAULT_CLUSTERS).split(','):
if 'localhost' not in api_server_url: if 'localhost' not in api_server_url:
@@ -152,6 +193,16 @@ def get_clusters():
except: except:
logging.exception('Failed to get metrics') logging.exception('Failed to get metrics')
clusters.append({'api_server_url': api_server_url, 'nodes': nodes, 'unassigned_pods': unassigned_pods}) 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=(',', ':')) return json.dumps({'kubernetes_clusters': clusters}, separators=(',', ':'))

View File

@@ -101,7 +101,7 @@ export default class App {
} }
animatePodCreation(originalPod, globalX, globalY) { animatePodCreation(originalPod, globalX, globalY) {
const pod = new Pod(originalPod.pod, this.tooltip, false) const pod = new Pod(originalPod.pod, this.tooltip, null)
pod.draw() pod.draw()
const targetPosition = new PIXI.Point(globalX, globalY) const targetPosition = new PIXI.Point(globalX, globalY)
const angle = Math.random()*Math.PI*2 const angle = Math.random()*Math.PI*2

View File

@@ -13,7 +13,7 @@ export default class Cluster extends PIXI.Graphics {
draw () { draw () {
var rows = [10, 10] var rows = [10, 10]
for (var node of this.cluster.nodes) { for (var node of this.cluster.nodes) {
var nodeBox = new Node(node, this.tooltip) var nodeBox = new Node(node, this, this.tooltip)
nodeBox.draw() nodeBox.draw()
if (nodeBox.isMaster()) { if (nodeBox.isMaster()) {
nodeBox.x = rows[0] nodeBox.x = rows[0]
@@ -30,7 +30,7 @@ export default class Cluster extends PIXI.Graphics {
for (const pod of this.cluster.unassigned_pods) { for (const pod of this.cluster.unassigned_pods) {
var podBox = Pod.getOrCreate(pod, this.tooltip) var podBox = Pod.getOrCreate(pod, this, this.tooltip)
podBox.x = rows[0] podBox.x = rows[0]
podBox.y = 20 podBox.y = 20
podBox.draw() podBox.draw()

View File

@@ -4,9 +4,10 @@ import {parseResource} from './utils.js'
const PIXI = require('pixi.js') const PIXI = require('pixi.js')
export default class Node extends PIXI.Graphics { export default class Node extends PIXI.Graphics {
constructor(node, tooltip) { constructor(node, cluster, tooltip) {
super() super()
this.node = node this.node = node
this.cluster = cluster
this.tooltip = tooltip this.tooltip = tooltip
} }
@@ -82,7 +83,7 @@ export default class Node extends PIXI.Graphics {
var py = 20 var py = 20
for (const pod of this.node.pods) { for (const pod of this.node.pods) {
if (pod.namespace != 'kube-system') { if (pod.namespace != 'kube-system') {
const podBox = Pod.getOrCreate(pod, this.tooltip) //new Pod(pod, this.tooltip) const podBox = Pod.getOrCreate(pod, this.cluster, this.tooltip) //new Pod(pod, this.tooltip)
podBox.x = px podBox.x = px
podBox.y = py podBox.y = py
nodeBox.addChild(podBox.draw()) nodeBox.addChild(podBox.draw())
@@ -98,7 +99,7 @@ export default class Node extends PIXI.Graphics {
py = 100 py = 100
for (const pod of this.node.pods) { for (const pod of this.node.pods) {
if (pod.namespace == 'kube-system') { if (pod.namespace == 'kube-system') {
const podBox = Pod.getOrCreate(pod, this.tooltip) //new Pod(pod, this.tooltip) const podBox = Pod.getOrCreate(pod, this.cluster, this.tooltip) //new Pod(pod, this.tooltip)
podBox.x = px podBox.x = px
podBox.y = py podBox.y = py
nodeBox.addChild(podBox.draw()) nodeBox.addChild(podBox.draw())

View File

@@ -5,15 +5,15 @@ export const ALL_PODS = {}
export class Pod extends PIXI.Graphics { export class Pod extends PIXI.Graphics {
constructor(pod, tooltip, register=true) { constructor(pod, tooltip, cluster) {
super() super()
this.pod = pod this.pod = pod
this.tooltip = tooltip this.tooltip = tooltip
this.tick = null this.tick = null
this._progress = 1 this._progress = 1
if (register) { if (cluster) {
ALL_PODS[pod.namespace + '/' + pod.name] = this ALL_PODS[cluster.cluster.api_server_url + '/' + pod.namespace + '/' + pod.name] = this
} }
} }
@@ -51,8 +51,8 @@ export class Pod extends PIXI.Graphics {
} }
} }
static getOrCreate(pod, tooltip) { static getOrCreate(pod, cluster, tooltip) {
const existingPod = ALL_PODS[pod.namespace + '/' + pod.name] const existingPod = ALL_PODS[cluster.cluster.api_server_url + '/' + pod.namespace + '/' + pod.name]
if (existingPod) { if (existingPod) {
existingPod.pod = pod existingPod.pod = pod
existingPod.clear() existingPod.clear()

3
tox.ini Normal file
View File

@@ -0,0 +1,3 @@
[flake8]
max-line-length=120
ignore=E402