zuul-web: Simplify tenant list

Loading the tenant list on systems with many tenants and a large number
of items can take a considerable amount of time. This is because
calculating the project and queue counts is quite heavy.

These counts on the tenant overview page might not be very useful and
are not displayed at all on the tenant dropdown list.

This change removes these counts from the tenant overview page table.
Also, it changes the `/tenants` endpoint so that it just delivers a list
of names of the currently configured tenants, which loads considerably
faster.

Change-Id: I13bc149fb6c08f1111745100a9b54580d1b4b38b
This commit is contained in:
Benjamin Schanzel 2024-10-23 12:22:52 +02:00 committed by James E. Blair
parent ce3d948a62
commit 458c9f37b8
3 changed files with 9 additions and 90 deletions

View File

@ -267,56 +267,20 @@ class TestWeb(BaseTestWeb):
self.assertIn(key, data["web"][0])
def test_web_tenants(self):
"Test that we can retrieve JSON status info"
# Disable tenant list caching
self.web.web.api.cache_expiry = 0
self.add_base_changes()
self.executor_server.hold_jobs_in_build = True
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
A.addApproval('Code-Review', 2)
self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
self.waitUntilSettled()
self.executor_server.release('project-merge')
self.waitUntilSettled()
"Test that we can retrieve a JSON tenant list"
resp = self.get_url("api/tenants")
self.assertIn('Content-Length', resp.headers)
self.assertIn('Content-Type', resp.headers)
self.assertEqual(
'application/json; charset=utf-8', resp.headers['Content-Type'])
# self.assertIn('Access-Control-Allow-Origin', resp.headers)
# self.assertIn('Cache-Control', resp.headers)
# self.assertIn('Last-Modified', resp.headers)
data = resp.json()
self.assertEqual('tenant-one', data[0]['name'])
self.assertEqual(3, data[0]['projects'])
self.assertEqual(3, data[0]['queue'])
# release jobs and check if the queue size is 0
self.executor_server.hold_jobs_in_build = False
self.executor_server.release()
self.waitUntilSettled()
data = self.get_url("api/tenants").json()
self.assertEqual('tenant-one', data[0]['name'])
self.assertEqual(3, data[0]['projects'])
self.assertEqual(0, data[0]['queue'])
# test that non-live items are not counted
self.executor_server.hold_jobs_in_build = True
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
B = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
B.setDependsOn(A, 1)
self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
data = self.get_url("api/tenants").json()
self.assertEqual('tenant-one', data[0]['name'])
self.assertEqual(3, data[0]['projects'])
self.assertEqual(1, data[0]['queue'])
expected = [
{
'name': 'tenant-one',
},
]
self.assertEqual(expected, data)
def test_web_connections_list(self):
data = self.get_url('api/connections').json()

View File

@ -19,12 +19,10 @@ import { Link } from 'react-router-dom'
import {
BuildIcon,
CubeIcon,
CubesIcon,
DesktopIcon,
FolderIcon,
HomeIcon,
RepositoryIcon,
TrendUpIcon,
ThumbtackIcon,
} from '@patternfly/react-icons'
import {
@ -72,8 +70,6 @@ class TenantsPage extends React.Component {
{ title: (<Link to={'/t/' + tenant.name + '/builds'}>Builds</Link>) },
{ title: (<Link to={'/t/' + tenant.name + '/buildsets'}>Buildsets</Link>) },
{ title: (<Link to={'/t/' + tenant.name + '/autoholds'}>Autoholds</Link>) },
tenant.projects,
tenant.queue
]
}
})
@ -106,14 +102,6 @@ class TenantsPage extends React.Component {
title: <IconProperty icon={<ThumbtackIcon />} value="Autoholds" />,
dataLabel: 'Autoholds',
},
{
title: <IconProperty icon={<CubesIcon />} value="Project count" />,
dataLabel: 'Project count',
},
{
title: <IconProperty icon={<TrendUpIcon />} value="Queue" />,
dataLabel: 'Queue',
}
]
return (

View File

@ -1339,27 +1339,6 @@ class ZuulWebAPI(object):
return {'zuul': {'admin': auth.admin,
'scope': [tenant_name, ]}, }
def _tenants(self):
result = []
with self.zuulweb.zk_context as ctx:
for tenant_name, tenant in sorted(
self.zuulweb.abide.tenants.items()):
queue_size = 0
for pipeline in tenant.layout.pipelines.values():
status = pipeline.summary.refresh(ctx)
for queue in status.get("change_queues", []):
for head in queue["heads"]:
for item in head:
if item["live"]:
queue_size += 1
result.append({
'name': tenant_name,
'projects': len(list(tenant.untrusted_projects)),
'queue': queue_size,
})
return result
@cherrypy.expose
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
@cherrypy.tools.handle_options()
@ -1370,26 +1349,14 @@ class ZuulWebAPI(object):
description='Returns the list of tenants',
schema=Prop('The list of tenants', [{
'name': Prop('Tenant name', str),
'projects': Prop('Tenant project count', int),
'queue': Prop('Active changes count', int),
}]),
)
@openapi_response(404, description='Tenant not found')
def tenants(self, auth):
cache_time = self.tenants_cache_time
if time.time() - cache_time > self.cache_expiry:
with self.tenants_cache_lock:
self.tenants_cache = self._tenants()
self.tenants_cache_time = time.time()
resp = cherrypy.response
resp.headers["Cache-Control"] = f"public, max-age={self.cache_expiry}"
last_modified = datetime.utcfromtimestamp(
self.tenants_cache_time
)
last_modified_header = last_modified.strftime('%a, %d %b %Y %X GMT')
resp.headers["Last-modified"] = last_modified_header
return self.tenants_cache
tenants = sorted(self.zuulweb.abide.tenants.keys())
return [{"name": t} for t in tenants]
@cherrypy.expose
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')