web: add /tenants route
This change adds the zuul:tenant_list to the scheduler gearman worker to expose the list of tenants. This change also adds the /tenants.json endpoint to the zuul-web as well as a web interface to list tenants at / or /tenants.html. Change-Id: Ia8edb52ec97ebe53205427c828944116eebe03b7
This commit is contained in:
parent
d646c12778
commit
e290f893a7
|
@ -56,6 +56,7 @@ class RPCListener(object):
|
|||
self.worker.registerFunction("zuul:promote")
|
||||
self.worker.registerFunction("zuul:get_running_jobs")
|
||||
self.worker.registerFunction("zuul:get_job_log_stream_address")
|
||||
self.worker.registerFunction("zuul:tenant_list")
|
||||
|
||||
def getFunctions(self):
|
||||
functions = {}
|
||||
|
@ -269,3 +270,10 @@ class RPCListener(object):
|
|||
job_log_stream_address['server'] = build.worker.hostname
|
||||
job_log_stream_address['port'] = build.worker.log_port
|
||||
job.sendWorkComplete(json.dumps(job_log_stream_address))
|
||||
|
||||
def handle_tenant_list(self, job):
|
||||
output = []
|
||||
for tenant_name, tenant in self.sched.abide.tenants.items():
|
||||
output.append({'name': tenant_name,
|
||||
'projects': len(tenant.untrusted_projects)})
|
||||
job.sendWorkComplete(json.dumps(output))
|
||||
|
|
|
@ -148,6 +148,31 @@ class LogStreamingHandler(object):
|
|||
return ws
|
||||
|
||||
|
||||
class GearmanHandler(object):
|
||||
log = logging.getLogger("zuul.web.GearmanHandler")
|
||||
|
||||
def __init__(self, rpc):
|
||||
self.rpc = rpc
|
||||
self.controllers = {
|
||||
'tenant_list': self.tenant_list,
|
||||
}
|
||||
|
||||
def tenant_list(self, request):
|
||||
job = self.rpc.submitJob('zuul:tenant_list', {})
|
||||
return web.json_response(json.loads(job.data[0]))
|
||||
|
||||
async def processRequest(self, request, action):
|
||||
try:
|
||||
resp = self.controllers[action](request)
|
||||
except asyncio.CancelledError:
|
||||
self.log.debug("request handling cancelled")
|
||||
except Exception as e:
|
||||
self.log.exception("exception:")
|
||||
resp = web.json_response({'error_description': 'Internal error'},
|
||||
status=500)
|
||||
return resp
|
||||
|
||||
|
||||
class ZuulWeb(object):
|
||||
|
||||
log = logging.getLogger("zuul.web.ZuulWeb")
|
||||
|
@ -163,11 +188,22 @@ class ZuulWeb(object):
|
|||
self.rpc = zuul.rpcclient.RPCClient(gear_server, gear_port,
|
||||
ssl_key, ssl_cert, ssl_ca)
|
||||
self.log_streaming_handler = LogStreamingHandler(self.rpc)
|
||||
self.gearman_handler = GearmanHandler(self.rpc)
|
||||
|
||||
async def _handleWebsocket(self, request):
|
||||
return await self.log_streaming_handler.processRequest(
|
||||
request)
|
||||
|
||||
async def _handleTenantsRequest(self, request):
|
||||
return await self.gearman_handler.processRequest(request,
|
||||
'tenant_list')
|
||||
|
||||
async def _handleStaticRequest(self, request):
|
||||
fp = None
|
||||
if request.path.endswith("tenants.html") or request.path.endswith("/"):
|
||||
fp = os.path.join(STATIC_DIR, "index.html")
|
||||
return web.FileResponse(fp)
|
||||
|
||||
def run(self, loop=None):
|
||||
"""
|
||||
Run the websocket daemon.
|
||||
|
@ -181,6 +217,9 @@ class ZuulWeb(object):
|
|||
"""
|
||||
routes = [
|
||||
('GET', '/console-stream', self._handleWebsocket),
|
||||
('GET', '/tenants.json', self._handleTenantsRequest),
|
||||
('GET', '/tenants.html', self._handleStaticRequest),
|
||||
('GET', '/', self._handleStaticRequest),
|
||||
]
|
||||
|
||||
self.log.debug("ZuulWeb starting")
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
External requirements needs to be installed in these locations
|
||||
* /static/js/angular.min.js
|
||||
* /static/js/jquery.min.js
|
||||
* /static/js/jquery-visibility.min.js
|
||||
* /static/js/jquery.graphite.min.js
|
||||
* /static/bootstrap/css/bootstrap.min.css
|
||||
|
||||
Here is an example apache vhost configuration:
|
||||
<VirtualHost zuul-web.example.com:80>
|
||||
DocumentRoot /var/www/zuul-web
|
||||
|
||||
LogLevel warn
|
||||
|
||||
Alias "/static" "/var/www/zuul-web"
|
||||
AliasMatch "^/.*/(.*).html" "/var/www/zuul-web/$1.html"
|
||||
AliasMatch "^/.*.html" "/var/www/zuul-web/index.html"
|
||||
<Directory /var/www/zuul-web>
|
||||
Require all granted
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
# Console-stream needs a special proxy-pass for websocket
|
||||
ProxyPass /console-stream ws://localhost:9000/console-stream nocanon retry=0
|
||||
ProxyPassReverse /console-stream ws://localhost:9000/console-stream
|
||||
|
||||
# Then only the json calls are sent to the zuul-web endpoints
|
||||
ProxyPassMatch ^/(.*.json)$ http://localhost:9000/$1 nocanon retry=0
|
||||
ProxyPassReverse / http://localhost:9000/
|
||||
</VirtualHost>
|
||||
|
||||
Then copy the zuul/web/static/ files and external requirements to
|
||||
/var/www/zuul-web
|
|
@ -0,0 +1,57 @@
|
|||
<!--
|
||||
Copyright 2017 Red Hat
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Zuul Tenants</title>
|
||||
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="static/styles/zuul.css" />
|
||||
<script src="/static/js/jquery.min.js"></script>
|
||||
<script src="/static/js/angular.min.js"></script>
|
||||
<script src="static/javascripts/zuul.angular.js"></script>
|
||||
</head>
|
||||
<body ng-app="zuulTenants" ng-controller="mainController"><div class="container-fluid">
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand">Zuul Dashboard</a>
|
||||
</div>
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="active"><a href="tenants.html">Tenants</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<table class="table table-hover table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Jobs</th>
|
||||
<th>Builds</th>
|
||||
<th>Projects count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="tenant in tenants">
|
||||
<td>{{ tenant.name }}</td>
|
||||
<td><a href="{{ tenant.name }}/status.html">status</a></td>
|
||||
<td><a href="{{ tenant.name }}/jobs.html">jobs</a></td>
|
||||
<td><a href="{{ tenant.name }}/builds.html">builds</a></td>
|
||||
<td>{{ tenant.projects }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></body></html>
|
|
@ -0,0 +1,32 @@
|
|||
// @licstart The following is the entire license notice for the
|
||||
// JavaScript code in this page.
|
||||
//
|
||||
// Copyright 2017 Red Hat
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
// @licend The above is the entire license notice
|
||||
// for the JavaScript code in this page.
|
||||
|
||||
angular.module('zuulTenants', []).controller(
|
||||
'mainController', function($scope, $http)
|
||||
{
|
||||
$scope.tenants = undefined;
|
||||
$scope.tenants_fetch = function() {
|
||||
$http.get("tenants.json")
|
||||
.then(function success(result) {
|
||||
$scope.tenants = result.data;
|
||||
});
|
||||
}
|
||||
$scope.tenants_fetch();
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
.zuul-change {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.zuul-change-id {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.zuul-job-result {
|
||||
float: right;
|
||||
width: 70px;
|
||||
height: 15px;
|
||||
margin: 2px 0 0 0;
|
||||
}
|
||||
|
||||
.zuul-change-total-result {
|
||||
height: 10px;
|
||||
width: 100px;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.zuul-spinner,
|
||||
.zuul-spinner:hover {
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease-out;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.zuul-spinner-on,
|
||||
.zuul-spinner-on:hover {
|
||||
opacity: 1;
|
||||
transition-duration: 0.2s;
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
.zuul-change-cell {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.zuul-change-job {
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.zuul-job-name {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.zuul-non-voting-desc {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.zuul-patchset-header {
|
||||
font-size: small;
|
||||
padding: 8px 12px;
|
||||
}
|
Loading…
Reference in New Issue