Add ability for monasca-admin to get cross tenant metrics

This change is for java only for now, will port the change
to python once vertica is supported in python api.

Change-Id: Ie7a27598c52f357898df6e7659a0ead4b75b5d34
This commit is contained in:
bklei 2015-06-25 08:45:25 -06:00
parent 5377aa6cb0
commit 9c243ba04f
7 changed files with 129 additions and 49 deletions

View File

@ -470,28 +470,28 @@ An alarm expression is a boolean equation which if it evaluates to true with the
At the highest level, you have an expression, which is made up of one or more subexpressions, joined by boolean operators. Parenthesis can be used around groups of subexpressions to indicated higher precedence. In a BNF style format where items enclosed in [] are optional, '*' means zero or more times, and '|' means or.
````
<expression>
::= <subexpression> [(and | or) <subexpression>]*
<expression>
::= <subexpression> [(and | or) <subexpression>]*
````
More formally, taking boolean operator precedence into account, where 'and' has higher precedence than 'or', results in the following.
````
<expression>
::= <and_expression> <or_logical_operator> <expression>
| <and_expression>
::= <and_expression> <or_logical_operator> <expression>
| <and_expression>
<and_expression>
::= <sub_expression> <and_logical_operator> <and_expression>
| <sub_expression>
::= <sub_expression> <and_logical_operator> <and_expression>
| <sub_expression>
````
Each subexpression is made up of several parts with a couple of options:
````
<sub_expression>
::= <function> '(' <metric> [',' period] ')' <relational_operator> threshold_value ['times' periods]
| '(' expression ')'
<sub_expression>
::= <function> '(' <metric> [',' period] ')' <relational_operator> threshold_value ['times' periods]
| '(' expression ')'
````
Period must be an integer multiple of 60. The default period is 60 seconds.
@ -508,17 +508,17 @@ A metric can be a metric name only or a metric name followed by a list of dimens
````
<metric>
::= metric_name
| metric_name '{' <dimension_list> '}'
::= metric_name
| metric_name '{' <dimension_list> '}'
````
Any number of dimensions can follow the metric name.
````
<dimension_list>
::= <dimension>
| <dimension> ',' <dimension_list>
::= <dimension>
| <dimension> ',' <dimension_list>
````
@ -526,8 +526,8 @@ A dimension is simply a key-value pair.
````
<dimension>
::= dimension_name '=' dimension_value
::= dimension_name '=' dimension_value
````
The relational_operators are: `lt` (also `<`), `gt` (also `>`), `lte` (also `<=`), `gte` (also `>=`).
@ -535,15 +535,15 @@ The relational_operators are: `lt` (also `<`), `gt` (also `>`), `lte` (also `<=`
````
<relational_operator>
::= 'lt' | '<' | 'gt' | '>' | 'lte' | '<=' | 'gte' | '>='
::= 'lt' | '<' | 'gt' | '>' | 'lte' | '<=' | 'gte' | '>='
````
The list of available statistical functions include the following.
```
<function>
::= 'min' | 'max' | 'sum' | 'count' | 'avg'
::= 'min' | 'max' | 'sum' | 'count' | 'avg'
```
where 'avg' is the arithmetic average. Note, threshold values are always in the same units as the metric that they are being compared to.
@ -836,7 +836,7 @@ Returns a JSON version object with details about the specified version.
___
# Metrics
The metrics resource allows metrics to be created and queried. The `X-Auth-Token` is used to derive the tenant that submits metrics. Metrics are stored and scoped to the tenant that submits them, or if the `tenant_id` query parameter is specified and the tenant has the `monitoring-delegate` role, the metrics are stored using the specified tenant ID.
The metrics resource allows metrics to be created and queried. The `X-Auth-Token` is used to derive the tenant that submits metrics. Metrics are stored and scoped to the tenant that submits them, or if the `tenant_id` query parameter is specified and the tenant has the `monitoring-delegate` role, the metrics are stored using the specified tenant ID. Note that several of the GET methods also support the tenant_id query parameter, but the `monasca-admin` role is required to get cross-tenant metrics, statistics, etc..
## Create Metric
Create metrics.
@ -851,7 +851,7 @@ Create metrics.
None.
#### Query Parameters
* tenant_id (string, optional, restricted) - Tenant ID to create metrics on behalf of. This parameter can be used to submit metrics from one tenant, to another. Normally, this parameter is used when the Agent is being run as an operational monitoring tenant, such as monitoring OpenStack infrastructure, and needs to submit metrics for an OpenStack resource, such as a VM, but those metrics need to be accessible to the tenant that owns the resource. Usage of this query parameter requires the `monitoring-delegate` role.
* tenant_id (string, optional, restricted) - Tenant ID to create metrics on behalf of. This parameter can be used to submit metrics from one tenant, to another. Normally, this parameter is used when the Agent is being run as an operational monitoring tenant, such as monitoring OpenStack infrastructure, and needs to submit metrics for an OpenStack resource, such as a VM, but those metrics need to be accessible to the tenant that owns the resource. Usage of this query parameter is restricted to users with the `monitoring-delegate` role.
#### Request Body
Consists of a single metric object or an array of metric objects. A metric has the following properties:
@ -969,6 +969,7 @@ Get metrics
None.
#### Query Parameters
* tenant_id (string, optional, restricted) - Tenant ID to from which to get metrics. This parameter can be used to get metrics from a tenant other than the tenant the request auth token is scoped to. Usage of this query parameter is restricted to users with the the monasca admin role, as defined in the monasca api configuration file, which defaults to `monasca-admin`.
* name (string(255), optional) - A metric name to filter metrics by.
* dimensions (string, optional) - A dictionary to filter metrics by specified as a comma separated array of (key, value) pairs as `key1:value1,key2:value2, ...`
* offset (integer (InfluxDB) or hexadecimal string (Vertica), optional)
@ -1049,6 +1050,7 @@ If users do not wish to see measurements for a single metric, but would prefer t
None.
#### Query Parameters
* tenant_id (string, optional, restricted) - Tenant ID from which to get measurements from. This parameter can be used to get metrics from a tenant other than the tenant the request auth token is scoped to. Usage of this query parameter is restricted to users with the monasca admin role, as defined in the monasca api configuration file, which defaults to `monasca-admin`.
* name (string(255), required) - A metric name to filter metrics by.
* dimensions (string, optional) - A dictionary to filter metrics by specified as a comma separated array of (key, value) pairs as `key1:value1,key2:value2, ...`
* start_time (string, required) - The start time in ISO 8601 combined date and time format in UTC.
@ -1150,6 +1152,7 @@ Get names for metrics.
None.
#### Query Parameters
* tenant_id (string, optional, restricted) - Tenant ID from which to get metric names. This parameter can be used to get metric names from a tenant other than the tenant the request auth token is scoped to. Usage of this query parameter is restricted to users with the monasca admin role, as defined in the monasca api configuration file, which defaults to `monasca-admin`.
* dimensions (string, optional) - A dictionary to filter metrics by specified as a comma separated array of (key, value) pairs as `key1:value1,key2:value2, ...`
* offset (integer, optional)
* limit (integer, optional)
@ -1220,6 +1223,7 @@ Get statistics for metrics.
None.
#### Query Parameters
* tenant_id (string, optional, restricted) - Tenant ID from which to get statistics. This parameter can be used to get statistics from a tenant other than the tenant the request auth token is scoped to. Usage of this query parameter is restricted to users with the monasca admin role, as defined in the monasca api configuration file, which defaults to `monasca-admin`.
* name (string(255), required) - A metric name to filter metrics by.
* dimensions (string, optional) - A dictionary to filter metrics by specified as a comma separated array of (key, value) pairs as `key1:value1,key2:value2, ...`
* statistics (string, required) - A comma separate array of statistics to evaluate. Valid statistics are avg, min, max, sum and count.

View File

@ -48,6 +48,8 @@ public final class Validation {
private Validation() {}
public static final String DEFAULT_ADMIN_ROLE = "monasca-admin";
/**
* @throws JsonMappingException if the {@code value} is not valid for the {@code type}
*/
@ -186,4 +188,36 @@ public final class Validation {
}
}
}
/**
* Convenience method for checking cross project access
*/
public static String getQueryProject(String roles,
String crossTenantId,
String tenantId,
String admin_role) throws Exception
{
String queryTenantId = tenantId;
boolean isAdmin = !Strings.isNullOrEmpty(roles) &&
COMMA_SPLITTER.splitToList(roles).contains(admin_role);
if (isCrossProjectRequest(crossTenantId, tenantId)) {
if (isAdmin) {
queryTenantId = crossTenantId;
} else {
throw Exceptions.forbidden("Only users with %s role can GET cross tenant metrics",
admin_role);
}
}
return queryTenantId;
}
/**
* Convenience method for determining if request is across projects.
*/
public static boolean isCrossProjectRequest(String crossTenantId, String tenantId) {
return !Strings.isNullOrEmpty(crossTenantId) && !crossTenantId.equals(tenantId);
}
}

View File

@ -51,6 +51,8 @@ public class MiddlewareConfiguration {
@JsonProperty
public String delegateAuthorizedRole;
@JsonProperty
public String adminRole;
@JsonProperty
public String timeToCacheToken = "600";
@JsonProperty
public String adminAuthMethod;

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
*
* 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
@ -13,6 +13,8 @@
*/
package monasca.api.resource;
import static monasca.api.app.validation.Validation.DEFAULT_ADMIN_ROLE;
import com.google.common.base.Strings;
import com.codahale.metrics.annotation.Timed;
@ -32,6 +34,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import monasca.api.app.validation.MetricNameValidation;
import monasca.api.ApiConfig;
import monasca.api.app.validation.Validation;
import monasca.api.domain.model.measurement.MeasurementRepo;
import monasca.api.infrastructure.persistence.PersistUtils;
@ -44,9 +47,12 @@ public class MeasurementResource {
private final MeasurementRepo repo;
private final PersistUtils persistUtils;
private final String admin_role;
@Inject
public MeasurementResource(MeasurementRepo repo, PersistUtils persistUtils) {
public MeasurementResource(ApiConfig config, MeasurementRepo repo, PersistUtils persistUtils) {
this.admin_role = (config.middleware == null || config.middleware.adminRole == null)
? DEFAULT_ADMIN_ROLE : config.middleware.adminRole;
this.repo = repo;
this.persistUtils = persistUtils;
}
@ -57,12 +63,14 @@ public class MeasurementResource {
public Object get(
@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId,
@HeaderParam("X-Roles") String roles,
@QueryParam("name") String name,
@QueryParam("dimensions") String dimensionsStr,
@QueryParam("start_time") String startTimeStr,
@QueryParam("end_time") String endTimeStr,
@QueryParam("offset") String offset,
@QueryParam("limit") String limit,
@QueryParam("tenant_id") String crossTenantId,
@QueryParam("merge_metrics") String mergeMetricsFlag) throws Exception {
// Validate query parameters
@ -76,8 +84,10 @@ public class MeasurementResource {
MetricNameValidation.validate(name, true);
Boolean mergeMetricsFlagBool = Validation.validateAndParseMergeMetricsFlag(mergeMetricsFlag);
String queryTenantId = Validation.getQueryProject(roles, crossTenantId, tenantId, admin_role);
return Links.paginateMeasurements(this.persistUtils.getLimit(limit),
repo.find(tenantId, name, dimensions, startTime, endTime,
repo.find(queryTenantId, name, dimensions, startTime, endTime,
offset, this.persistUtils.getLimit(limit),
mergeMetricsFlagBool),
uriInfo);

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
*
* 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
@ -13,6 +13,8 @@
*/
package monasca.api.resource;
import static monasca.api.app.validation.Validation.DEFAULT_ADMIN_ROLE;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
@ -55,6 +57,7 @@ public class MetricResource {
private static final Splitter COMMA_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults();
private final String monitoring_delegate_role;
private final String admin_role;
private final MetricService service;
private final MetricDefinitionRepo metricRepo;
private final PersistUtils persistUtils;
@ -62,11 +65,13 @@ public class MetricResource {
@Inject
public MetricResource(ApiConfig config, MetricService service, MetricDefinitionRepo metricRepo,
PersistUtils persistUtils) {
if (config.middleware == null || config.middleware.delegateAuthorizedRole == null) {
this.monitoring_delegate_role = "monitoring-delegate";
} else {
this.monitoring_delegate_role = config.middleware.delegateAuthorizedRole;
}
this.monitoring_delegate_role = (config.middleware == null || config.middleware.delegateAuthorizedRole == null)
? "monitoring-delegate" : config.middleware.delegateAuthorizedRole;
this.admin_role = (config.middleware == null || config.middleware.adminRole == null)
? DEFAULT_ADMIN_ROLE : config.middleware.adminRole;
this.service = service;
this.metricRepo = metricRepo;
this.persistUtils = persistUtils;
@ -93,7 +98,7 @@ public class MetricResource {
.forbidden("Project %s cannot POST metrics for the hpcs service", tenantId);
}
}
if (!Strings.isNullOrEmpty(crossTenantId) && !crossTenantId.equals(tenantId)) {
if (Validation.isCrossProjectRequest(crossTenantId, tenantId)) {
throw Exceptions.forbidden("Project %s cannot POST cross tenant metrics", tenantId);
}
}
@ -109,19 +114,22 @@ public class MetricResource {
@Timed
@Produces(MediaType.APPLICATION_JSON)
public Object getMetrics(@Context UriInfo uriInfo, @HeaderParam("X-Tenant-Id") String tenantId,
@HeaderParam("X-Roles") String roles,
@QueryParam("name") String name,
@QueryParam("dimensions") String dimensionsStr,
@QueryParam("offset") String offset,
@QueryParam("limit") String limit)
throws Exception {
@QueryParam("limit") String limit,
@QueryParam("tenant_id") String crossTenantId) throws Exception
{
Map<String, String>
dimensions =
Strings.isNullOrEmpty(dimensionsStr) ? null : Validation
.parseAndValidateDimensions(dimensionsStr);
MetricNameValidation.validate(name, false);
String queryTenantId = Validation.getQueryProject(roles, crossTenantId, tenantId, admin_role);
return Links.paginate(this.persistUtils.getLimit(limit),
metricRepo.find(tenantId, name, dimensions, offset, this.persistUtils.getLimit(limit)), uriInfo);
metricRepo.find(queryTenantId, name, dimensions, offset, this.persistUtils.getLimit(limit)), uriInfo);
}
@GET
@ -130,15 +138,25 @@ public class MetricResource {
@Produces(MediaType.APPLICATION_JSON)
public Object getMetricNames(@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId,
@HeaderParam("X-Roles") String roles,
@QueryParam("dimensions") String dimensionsStr,
@QueryParam("offset") String offset,
@QueryParam("limit") String limit) throws Exception {
@QueryParam("limit") String limit,
@QueryParam("tenant_id") String crossTenantId) throws Exception
{
Map<String, String>
dimensions =
Strings.isNullOrEmpty(dimensionsStr) ? null : Validation
.parseAndValidateDimensions(dimensionsStr);
String queryTenantId = Validation.getQueryProject(roles, crossTenantId, tenantId, admin_role);
return Links.paginate(this.persistUtils.getLimit(limit),
metricRepo.findNames(tenantId, dimensions, offset, this.persistUtils.getLimit(limit)), uriInfo);
metricRepo.findNames(queryTenantId,
dimensions,
offset,
this.persistUtils.getLimit(limit)),
uriInfo);
}
}

View File

@ -1,11 +1,11 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
*
* 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
@ -13,6 +13,8 @@
*/
package monasca.api.resource;
import static monasca.api.app.validation.Validation.DEFAULT_ADMIN_ROLE;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
@ -35,6 +37,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import monasca.api.app.validation.MetricNameValidation;
import monasca.api.ApiConfig;
import monasca.api.app.validation.Validation;
import monasca.api.domain.model.statistic.StatisticRepo;
import monasca.api.infrastructure.persistence.PersistUtils;
@ -47,12 +50,14 @@ import monasca.api.infrastructure.persistence.PersistUtils;
@Path("/v2.0/metrics/statistics")
public class StatisticResource {
private static final Splitter COMMA_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults();
private final String admin_role;
private final StatisticRepo repo;
private final PersistUtils persistUtils;
@Inject
public StatisticResource(StatisticRepo repo, PersistUtils persistUtils) {
public StatisticResource(ApiConfig config, StatisticRepo repo, PersistUtils persistUtils) {
this.admin_role = (config.middleware == null || config.middleware.adminRole == null)
? DEFAULT_ADMIN_ROLE : config.middleware.adminRole;
this.repo = repo;
this.persistUtils = persistUtils;
}
@ -64,6 +69,7 @@ public class StatisticResource {
public Object get(
@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId,
@HeaderParam("X-Roles") String roles,
@QueryParam("name") String name,
@QueryParam("dimensions") String dimensionsStr,
@QueryParam("start_time") String startTimeStr,
@ -72,6 +78,7 @@ public class StatisticResource {
@DefaultValue("300") @QueryParam("period") String periodStr,
@QueryParam("offset") String offset,
@QueryParam("limit") String limit,
@QueryParam("tenant_id") String crossTenantId,
@QueryParam("merge_metrics") String mergeMetricsFlag) throws Exception {
// Validate query parameters
@ -89,8 +96,10 @@ public class StatisticResource {
MetricNameValidation.validate(name, true);
Boolean mergeMetricsFlagBool = Validation.validateAndParseMergeMetricsFlag(mergeMetricsFlag);
String queryTenantId = Validation.getQueryProject(roles, crossTenantId, tenantId, admin_role);
return Links.paginateStatistics(this.persistUtils.getLimit(limit),
repo.find(tenantId, name, dimensions, startTime, endTime,
repo.find(queryTenantId, name, dimensions, startTime, endTime,
statistics, period, offset,
this.persistUtils.getLimit(limit),
mergeMetricsFlagBool),

View File

@ -28,6 +28,7 @@ import java.util.Map;
import org.joda.time.DateTime;
import org.testng.annotations.Test;
import monasca.api.ApiConfig;
import monasca.api.domain.model.statistic.StatisticRepo;
import monasca.api.infrastructure.persistence.PersistUtils;
@ -36,6 +37,7 @@ import com.sun.jersey.api.client.ClientResponse;
@Test
public class StatisticResourceTest extends AbstractMonApiResourceTest {
private StatisticRepo statisticRepo;
private ApiConfig apiConfig;
long timestamp;
@Override
@ -43,7 +45,8 @@ public class StatisticResourceTest extends AbstractMonApiResourceTest {
super.setupResources();
statisticRepo = mock(StatisticRepo.class);
addResources(new StatisticResource(statisticRepo, new PersistUtils()));
apiConfig = mock(ApiConfig.class);
addResources(new StatisticResource(apiConfig, statisticRepo, new PersistUtils()));
}
@SuppressWarnings("unchecked")