diff --git a/src/frontend/app/features/cloud-foundry/services/cloud-foundry-organization.service.ts b/src/frontend/app/features/cloud-foundry/services/cloud-foundry-organization.service.ts index b548239c7c..08bfe1b395 100644 --- a/src/frontend/app/features/cloud-foundry/services/cloud-foundry-organization.service.ts +++ b/src/frontend/app/features/cloud-foundry/services/cloud-foundry-organization.service.ts @@ -75,7 +75,7 @@ export class CloudFoundryOrganizationService { } private initialiseObservables() { - this.org$ = this.cfUserService.isConnectedUserAdmin(this.store, this.cfGuid).pipe( + this.org$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { const relations = [ createEntityRelationKey(organizationSchemaKey, spaceSchemaKey), @@ -143,7 +143,7 @@ export class CloudFoundryOrganizationService { this.quotaDefinition$ = this.org$.pipe(map(o => o.entity.entity.quota_definition && o.entity.entity.quota_definition.entity)); - this.allOrgUsers$ = this.cfUserService.isConnectedUserAdmin(this.store, this.cfGuid).pipe( + this.allOrgUsers$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { const action = new GetAllOrgUsers(this.orgGuid, this.usersPaginationKey, this.cfGuid, isAdmin); return getPaginationObservables>({ diff --git a/src/frontend/app/features/cloud-foundry/services/cloud-foundry-space.service.ts b/src/frontend/app/features/cloud-foundry/services/cloud-foundry-space.service.ts index 61d304c945..597a4e82ce 100644 --- a/src/frontend/app/features/cloud-foundry/services/cloud-foundry-space.service.ts +++ b/src/frontend/app/features/cloud-foundry/services/cloud-foundry-space.service.ts @@ -100,7 +100,7 @@ export class CloudFoundrySpaceService { } private initialiseSpaceObservables() { - this.space$ = this.cfUserService.isConnectedUserAdmin(this.store, this.cfGuid).pipe( + this.space$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { const relations = [ createEntityRelationKey(spaceSchemaKey, applicationSchemaKey), @@ -143,7 +143,7 @@ export class CloudFoundrySpaceService { } })); - this.allSpaceUsers$ = this.cfUserService.isConnectedUserAdmin(this.store, this.cfGuid).pipe( + this.allSpaceUsers$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { const action = new GetAllSpaceUsers(this.spaceGuid, this.usersPaginationKey, this.cfGuid, isAdmin); return getPaginationObservables({ diff --git a/src/frontend/app/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts b/src/frontend/app/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts index 844787aaa1..6f9f5d8482 100644 --- a/src/frontend/app/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts +++ b/src/frontend/app/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts @@ -8,6 +8,7 @@ import { entityFactory, metricSchemaKey } from '../../../../../../store/helpers/ import { getActiveRouteCfCellProvider } from '../../../../cf.helpers'; import { CloudFoundryEndpointService } from '../../../../services/cloud-foundry-endpoint.service'; import { CloudFoundryCellService } from '../cloud-foundry-cell.service'; +import { CfUserService } from '../../../../../../shared/data-services/cf-user.service'; @Component({ selector: 'app-cloud-foundry-cell-base', @@ -20,6 +21,8 @@ import { CloudFoundryCellService } from '../cloud-foundry-cell.service'; }) export class CloudFoundryCellBaseComponent { + static AppsLinks = 'apps'; + tabLinks: ISubHeaderTabs[] = [ { link: 'summary', @@ -30,7 +33,7 @@ export class CloudFoundryCellBaseComponent { label: 'Metrics' }, { - link: 'apps', + link: CloudFoundryCellBaseComponent.AppsLinks, label: 'App Instances' }, ]; @@ -65,5 +68,10 @@ export class CloudFoundryCellBaseComponent { ])), first() ); + + this.tabLinks.find(link => link.link === CloudFoundryCellBaseComponent.AppsLinks).hidden = + cfEndpointService.currentUser$.pipe( + map(user => !user.admin) + ); } } diff --git a/src/frontend/app/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-source.ts b/src/frontend/app/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-source.ts index b8a99df869..f8d76cc506 100644 --- a/src/frontend/app/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-source.ts +++ b/src/frontend/app/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-source.ts @@ -5,7 +5,7 @@ import { map } from 'rxjs/operators'; import { IApp } from '../../../../../core/cf-api.types'; import { EntityServiceFactory } from '../../../../../core/entity-service-factory.service'; import { GetApplication } from '../../../../../store/actions/application.actions'; -import { FetchCFCellMetricsPaginatedAction, MetricQueryConfig } from '../../../../../store/actions/metrics.actions'; +import { FetchCFMetricsPaginatedAction, MetricQueryConfig } from '../../../../../store/actions/metrics.actions'; import { AppState } from '../../../../../store/app-state'; import { applicationSchemaKey, @@ -40,9 +40,9 @@ export class CfCellAppsDataSource listConfig: IListConfig, entityServiceFactory: EntityServiceFactory ) { - const action = new FetchCFCellMetricsPaginatedAction( - cfGuid, + const action = new FetchCFMetricsPaginatedAction( cellId, + cfGuid, new MetricQueryConfig(`firehose_container_metric_cpu_percentage{bosh_job_id="${cellId}"}`, {}), MetricQueryType.QUERY ); diff --git a/src/frontend/app/shared/components/list/list-types/cf-cells/cf-cells-data-source.ts b/src/frontend/app/shared/components/list/list-types/cf-cells/cf-cells-data-source.ts index 2a1387dfd2..2077680205 100644 --- a/src/frontend/app/shared/components/list/list-types/cf-cells/cf-cells-data-source.ts +++ b/src/frontend/app/shared/components/list/list-types/cf-cells/cf-cells-data-source.ts @@ -20,6 +20,7 @@ export class CfCellsDataSource constructor(store: Store, cfGuid: string, listConfig: IListConfig>) { const action = new FetchCFMetricsPaginatedAction( + cfGuid, cfGuid, new MetricQueryConfig('firehose_value_metric_rep_unhealthy_cell', {}), MetricQueryType.QUERY diff --git a/src/frontend/app/shared/data-services/cf-user.service.ts b/src/frontend/app/shared/data-services/cf-user.service.ts index 222f6f9f27..49c688b6f3 100644 --- a/src/frontend/app/shared/data-services/cf-user.service.ts +++ b/src/frontend/app/shared/data-services/cf-user.service.ts @@ -332,7 +332,7 @@ export class CfUserService { })); } - public isConnectedUserAdmin = (store, cfGuid: string): Observable => + public isConnectedUserAdmin = (cfGuid: string): Observable => this.store.select(getCurrentUserCFGlobalStates(cfGuid)).pipe( filter(state => !!state), map(state => state.isAdmin), diff --git a/src/frontend/app/shared/services/metrics-range-selector.service.ts b/src/frontend/app/shared/services/metrics-range-selector.service.ts index 03ce4824f0..6fd2a3294e 100644 --- a/src/frontend/app/shared/services/metrics-range-selector.service.ts +++ b/src/frontend/app/shared/services/metrics-range-selector.service.ts @@ -81,7 +81,7 @@ export class MetricsRangeSelectorService { }; } else { return { - timeRange: metrics.query.params.window ? + timeRange: metrics.query.params && metrics.query.params.window ? times.find(time => time.value === metrics.query.params.window) : this.getDefaultTimeRange(times) }; diff --git a/src/frontend/app/store/actions/metrics.actions.ts b/src/frontend/app/store/actions/metrics.actions.ts index dd363702ca..b34fdda895 100644 --- a/src/frontend/app/store/actions/metrics.actions.ts +++ b/src/frontend/app/store/actions/metrics.actions.ts @@ -64,11 +64,12 @@ export class MetricsAction implements IRequestAction { export class FetchCFMetricsAction extends MetricsAction { constructor( + guid: string, cfGuid: string, public query: MetricQueryConfig, queryType: MetricQueryType = MetricQueryType.QUERY, isSeries = true) { - super(cfGuid, cfGuid, query, `${MetricsAction.getBaseMetricsURL()}/cf`, queryType, isSeries); + super(guid, cfGuid, query, `${MetricsAction.getBaseMetricsURL()}/cf`, queryType, isSeries); } } @@ -79,13 +80,17 @@ export class FetchCFCellMetricsAction extends MetricsAction { public query: MetricQueryConfig, queryType: MetricQueryType = MetricQueryType.QUERY, isSeries = true) { - super(cfGuid + '-' + cellId, cfGuid, query, `${MetricsAction.getBaseMetricsURL()}/cf`, queryType, isSeries); + super(cfGuid + '-' + cellId, cfGuid, query, `${MetricsAction.getBaseMetricsURL()}/cf/cells`, queryType, isSeries); } } export class FetchCFMetricsPaginatedAction extends FetchCFMetricsAction implements PaginatedAction { - constructor(cfGuid: string, public query: MetricQueryConfig, queryType: MetricQueryType = MetricQueryType.QUERY) { - super(cfGuid, query, queryType); + constructor( + guid: string, + cfGuid: string, + public query: MetricQueryConfig, + queryType: MetricQueryType = MetricQueryType.QUERY) { + super(guid, cfGuid, query, queryType); this.paginationKey = this.metricId; } actions = []; diff --git a/src/jetstream/plugins/metrics/cloud_foundry.go b/src/jetstream/plugins/metrics/cloud_foundry.go index b48b84d9e2..21f5aba212 100644 --- a/src/jetstream/plugins/metrics/cloud_foundry.go +++ b/src/jetstream/plugins/metrics/cloud_foundry.go @@ -11,12 +11,25 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" ) +var ( + cellQueryWhiteList = []string{ + "firehose_value_metric_rep_unhealthy_cell", + "firehose_value_metric_rep_capacity_remaining_containers", + "firehose_value_metric_rep_capacity_remaining_disk", + "firehose_value_metric_rep_capacity_remaining_memory", + "firehose_value_metric_rep_capacity_total_containers", + "firehose_value_metric_rep_capacity_total_disk", + "firehose_value_metric_rep_capacity_total_memory", + "firehose_value_metric_rep_num_cpus", + } +) + // Metrics endpoints - non-admin - for a Cloud Foundry Application func (m *MetricsSpecification) getCloudFoundryAppMetrics(c echo.Context) error { // We need to go and fetch the CF App, to make sure that the user is permitted to access it - // We'll do this synchronously here for now - this can be done optimistically in parallel in t he future + // We'll do this synchronously here for now - this can be done optimistically in parallel in the future // Use the passthrough mechanism to get the App metadata from Cloud Foundry appID := c.Param("appId") @@ -41,22 +54,7 @@ func (m *MetricsSpecification) getCloudFoundryAppMetrics(c echo.Context) error { } } - // get the user - userGUID, err := m.portalProxy.GetSessionStringValue(c, "user_id") - if err != nil { - return errors.New("Could not find session user_id") - } - - // For each CNSI, find the metrics endpoint that we need to talk to - metrics, err2 := m.getMetricsEndpoints(userGUID, cnsiList) - if err2 != nil { - return errors.New("Can not get metric endpoint metadata") - } - - // Construct the metadata for proxying - requests := makePrometheusRequestInfos(c, userGUID, metrics, prometheusOp, "application_id=\""+appID+"\"") - responses, err = m.portalProxy.DoProxyRequest(requests) - return m.portalProxy.SendProxiedResponse(c, responses) + return m.makePrometheusRequest(c, cnsiList, "application_id=\""+appID+"\"") } func makePrometheusRequestInfos(c echo.Context, userGUID string, metrics map[string]EndpointMetricsRelation, prometheusOp string, queries string) []interfaces.ProxyRequestInfo { @@ -113,9 +111,13 @@ func getEchoURL(c echo.Context) url.URL { // Metrics API endpoints - admin - for a Cloud Foundry deployment func (m *MetricsSpecification) getCloudFoundryMetrics(c echo.Context) error { + cnsiList := strings.Split(c.Request().Header().Get("x-cap-cnsi-list"), ",") + return m.makePrometheusRequest(c, cnsiList, "") +} + +func (m *MetricsSpecification) makePrometheusRequest(c echo.Context, cnsiList []string, queries string) error { prometheusOp := c.Param("op") - cnsiList := strings.Split(c.Request().Header().Get("x-cap-cnsi-list"), ",") // get the user userGUID, err := m.portalProxy.GetSessionStringValue(c, "user_id") @@ -130,7 +132,33 @@ func (m *MetricsSpecification) getCloudFoundryMetrics(c echo.Context) error { } // Construct the metadata for proxying - requests := makePrometheusRequestInfos(c, userGUID, metrics, prometheusOp, "") + requests := makePrometheusRequestInfos(c, userGUID, metrics, prometheusOp, queries) responses, err := m.portalProxy.DoProxyRequest(requests) return m.portalProxy.SendProxiedResponse(c, responses) } + +func isAllowedCellMetricsQuery(query string) bool { + for _, whiteListQuery := range cellQueryWhiteList { + if strings.Index(query, whiteListQuery) == 0 { + return true + } + } + return false +} + +// Metrics endpoints - cells - with white list of cell prometheus query values +func (m *MetricsSpecification) getCloudFoundryCellMetrics(c echo.Context) error { + + uri := getEchoURL(c) + values := uri.Query() + query := values.Get("query") + + // Fail all queries that are not of type 'cell' + if !isAllowedCellMetricsQuery(query) { + return errors.New("Unsupported prometheus query") + } + + cnsiList := strings.Split(c.Request().Header().Get("x-cap-cnsi-list"), ",") + + return m.makePrometheusRequest(c, cnsiList, "") +} diff --git a/src/jetstream/plugins/metrics/main.go b/src/jetstream/plugins/metrics/main.go index fb49f3a1b6..ce5bb22c08 100644 --- a/src/jetstream/plugins/metrics/main.go +++ b/src/jetstream/plugins/metrics/main.go @@ -72,6 +72,7 @@ func (m *MetricsSpecification) AddAdminGroupRoutes(echoContext *echo.Group) { // AddSessionGroupRoutes adds the session routes for this plugin to the Echo server func (m *MetricsSpecification) AddSessionGroupRoutes(echoContext *echo.Group) { echoContext.GET("/metrics/cf/app/:appId/:op", m.getCloudFoundryAppMetrics) + echoContext.GET("/metrics/cf/cells/:op", m.getCloudFoundryCellMetrics) } func (m *MetricsSpecification) GetType() string {