Implement get all installed extensions (#6391)

Introduce new table in the control plane view of the dashboard to display installed extensions.

Signed-off-by: Sanni Michael <sannimichaelse@gmail.com>
This commit is contained in:
Sanni Michael 2021-08-16 10:51:29 +01:00 committed by GitHub
parent 8c9930b4ac
commit fb0b1c898e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 9 deletions

View File

@ -34,6 +34,17 @@ const styles = {
}, },
}; };
const installedExtensionsColumn = [
{
title: <Trans>columnTitleName</Trans>,
dataIndex: 'name',
},
{
title: <Trans>columnTitleNamespace</Trans>,
dataIndex: 'namespace',
},
];
const serviceMeshDetailsColumns = [ const serviceMeshDetailsColumns = [
{ {
title: <Trans>columnTitleName</Trans>, title: <Trans>columnTitleName</Trans>,
@ -72,6 +83,7 @@ class ServiceMesh extends React.Component {
this.state = { this.state = {
pollingInterval: 2000, pollingInterval: 2000,
components: [], components: [],
extensions: [],
nsStatuses: [], nsStatuses: [],
pendingRequests: false, pendingRequests: false,
loaded: false, loaded: false,
@ -81,6 +93,7 @@ class ServiceMesh extends React.Component {
componentDidMount() { componentDidMount() {
this.startServerPolling(); this.startServerPolling();
this.fetchAllInstalledExtensions();
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
@ -109,6 +122,12 @@ class ServiceMesh extends React.Component {
]; ];
} }
getInstalledExtensions() {
const { extensions } = this.state;
const extensionList = !_isEmpty(extensions.extensions) ? extensions.extensions : [];
return extensionList;
}
getControllerComponentData = podData => { getControllerComponentData = podData => {
const podDataByDeploy = _groupBy(_filter(podData.pods, d => d.controlPlane), p => p.deployment); const podDataByDeploy = _groupBy(_filter(podData.pods, d => d.controlPlane), p => p.deployment);
const byDeployName = _mapKeys(podDataByDeploy, (_pods, dep) => dep.split('/')[1]); const byDeployName = _mapKeys(podDataByDeploy, (_pods, dep) => dep.split('/')[1]);
@ -190,6 +209,15 @@ class ServiceMesh extends React.Component {
.catch(this.handleApiError); .catch(this.handleApiError);
} }
fetchAllInstalledExtensions() {
this.api.setCurrentRequests([this.api.fetchExtension()]);
this.serverPromise = Promise.all(this.api.getCurrentPromises())
.then(([extensions]) => {
this.setState({ extensions });
})
.catch(this.handleApiError);
}
handleApiError(e) { handleApiError(e) {
if (e.isCanceled) { if (e.isCanceled) {
return; return;
@ -234,6 +262,23 @@ class ServiceMesh extends React.Component {
); );
} }
renderInstalledExtensions() {
return (
<React.Fragment>
<Grid container justify="space-between">
<Grid item xs={3}>
<Typography variant="h6"><Trans>Installed Extensions</Trans></Typography>
</Grid>
</Grid>
<BaseTable
tableClassName="metric-table"
tableRows={this.getInstalledExtensions()}
tableColumns={installedExtensionsColumn}
rowKey={d => d.uid} />
</React.Fragment>
);
}
renderServiceMeshDetails() { renderServiceMeshDetails() {
return ( return (
<React.Fragment> <React.Fragment>
@ -298,6 +343,7 @@ class ServiceMesh extends React.Component {
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid item xs={8} container direction="column"> <Grid item xs={8} container direction="column">
<Grid item>{this.renderControlPlaneDetails()}</Grid> <Grid item>{this.renderControlPlaneDetails()}</Grid>
<Grid item>{this.renderInstalledExtensions()}</Grid>
<Grid item> <Grid item>
<MeshedStatusTable tableRows={nsStatuses} /> <MeshedStatusTable tableRows={nsStatuses} />
</Grid> </Grid>

View File

@ -61,7 +61,7 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
const servicesPath = '/api/services'; const servicesPath = '/api/services';
const edgesPath = '/api/edges'; const edgesPath = '/api/edges';
const gatewaysPath = '/api/gateways'; const gatewaysPath = '/api/gateways';
const l5dExtensionsPath = '/api/extension'; const l5dExtensionsPath = '/api/extensions';
const validMetricsWindows = { const validMetricsWindows = {
'10s': '10 minutes', '10s': '10 minutes',
@ -147,7 +147,11 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
}; };
const fetchExtension = name => { const fetchExtension = name => {
return apiFetch(`${l5dExtensionsPath}?extension_name=${name}`); let extensionPath = l5dExtensionsPath;
if (name) {
extensionPath += `?extension_name=${name}`;
}
return apiFetch(extensionPath);
}; };
const fetchResourceDefinition = (namespace, resourceType, resourceName) => { const fetchResourceDefinition = (namespace, resourceType, resourceName) => {

View File

@ -434,23 +434,54 @@ func (h *handler) handleAPIResourceDefinition(w http.ResponseWriter, req *http.R
w.Write(resourceDefinition) w.Write(resourceDefinition)
} }
func (h *handler) handleGetExtension(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { func (h *handler) handleGetExtensions(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
ctx := req.Context() ctx := req.Context()
extensionName := req.FormValue("extension_name") extensionName := req.FormValue("extension_name")
type Extension struct {
Name string `json:"name"`
UID string `json:"uid"`
Namespace string `json:"namespace"`
}
resp := map[string]interface{}{} resp := map[string]interface{}{}
ns, err := h.k8sAPI.GetNamespaceWithExtensionLabel(ctx, extensionName) if extensionName != "" {
if err != nil && strings.HasPrefix(err.Error(), "could not find") { ns, err := h.k8sAPI.GetNamespaceWithExtensionLabel(ctx, extensionName)
if err != nil && strings.HasPrefix(err.Error(), "could not find") {
renderJSON(w, resp)
return
} else if err != nil {
renderJSONError(w, err, http.StatusInternalServerError)
return
}
resp["data"] = Extension{
UID: string(ns.UID),
Name: ns.GetLabels()[k8s.LinkerdExtensionLabel],
Namespace: ns.Name,
}
renderJSON(w, resp) renderJSON(w, resp)
return return
} else if err != nil { }
installedExtensions, err := h.k8sAPI.GetAllNamespacesWithExtensionLabel(ctx)
if err != nil {
renderJSONError(w, err, http.StatusInternalServerError) renderJSONError(w, err, http.StatusInternalServerError)
return return
} }
resp["extensionName"] = ns.GetLabels()[k8s.LinkerdExtensionLabel] extensionList := make([]Extension, len(installedExtensions))
resp["namespace"] = ns.Name
for i, installedExtension := range installedExtensions {
extensionList[i] = Extension{
UID: string(installedExtension.GetObjectMeta().GetUID()),
Name: installedExtension.GetLabels()[k8s.LinkerdExtensionLabel],
Namespace: installedExtension.GetName(),
}
}
resp["extensions"] = extensionList
renderJSON(w, resp) renderJSON(w, resp)
} }

View File

@ -196,7 +196,7 @@ func NewServer(
server.router.GET("/api/check", handler.handleAPICheck) server.router.GET("/api/check", handler.handleAPICheck)
server.router.GET("/api/resource-definition", handler.handleAPIResourceDefinition) server.router.GET("/api/resource-definition", handler.handleAPIResourceDefinition)
server.router.GET("/api/gateways", handler.handleAPIGateways) server.router.GET("/api/gateways", handler.handleAPIGateways)
server.router.GET("/api/extension", handler.handleGetExtension) server.router.GET("/api/extensions", handler.handleGetExtensions)
// grafana proxy // grafana proxy
server.handleAllOperationsForPath("/grafana/*grafanapath", handler.handleGrafana) server.handleAllOperationsForPath("/grafana/*grafanapath", handler.handleGrafana)