Enhance dimension filtering

Allow filtering for existence of dimension value, ex. all
metrics that have a service dimension, regardless of value

Change-Id: I3ffcae317e4d0bd03e8a02154858eea4afd25425
This commit is contained in:
Ryan Brandt 2016-01-12 10:01:25 -07:00
parent 0195c9162d
commit 7e6e6972fb
16 changed files with 291 additions and 38 deletions

View File

@ -992,7 +992,7 @@ 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, ...`
* dimensions (string, optional) - A dictionary to filter metrics by specified as a comma separated array of (key, value) pairs as `key1:value1,key2:value2, ...`, leaving the value empty `key1,key2:value2` will return all values for that key, multiple values for a key may be specified as `key1:value1|value2|...,key2:value4,...`
* start_time (string, optional) - The start time in ISO 8601 combined date and time format in UTC. This is useful for only listing metrics that have measurements since the specified start_time.
* end_time (string, optional) - The end time in ISO 8601 combined date and time format in UTC. Combined with start_time, this can be useful to only list metrics that have measurements in between the specified start_time and end_time.
* offset (integer (InfluxDB) or hexadecimal string (Vertica), optional)
@ -1785,7 +1785,7 @@ None.
#### Query Parameters
* name (string(255), optional) - Name of alarm to filter by.
* dimensions (string, optional) - Dimensions of metrics to filter by specified as a comma separated array of (key, value) pairs as `key1:value1,key1:value1, ...`
* dimensions (string, optional) - Dimensions of metrics to filter by specified as a comma separated array of (key, value) pairs as `key1:value1,key1:value1, ...`, leaving the value empty `key1,key2:value2` will return all values for that key, multiple values for a key may be specified as `key1:value1|value2|...,key2:value4,...`
* offset (integer, optional)
* limit (integer, optional)
* sort_by (string, optional) - Comma separated list of fields to sort by, defaults to 'id', 'created_at'. Fields may be followed by 'asc' or 'desc' to set the direction, ex 'severity desc'
@ -2251,7 +2251,7 @@ None.
* alarm_definition_id (string, optional) - Alarm definition ID to filter by.
* metric_name (string(255), optional) - Name of metric to filter by.
* metric_dimensions ({string(255): string(255)}, optional) - Dimensions of metrics to filter by specified as a comma separated array of (key, value) pairs as `key1:value1,key1:value1, ...`
* metric_dimensions ({string(255): string(255)}, optional) - Dimensions of metrics to filter by specified as a comma separated array of (key, value) pairs as `key1:value1,key1:value1, ...`, leaving the value empty `key1,key2:value2` will return all values for that key, multiple values for a key may be specified as `key1:value1|value2|...,key2:value4,...`
* state (string, optional) - State of alarm to filter by, either `OK`, `ALARM` or `UNDETERMINED`.
* lifecycle_state (string(50), optional) - Lifecycle state to filter by.
* link (string(512), optional) - Link to filter by.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
* Copyright (c) 2014,2016 Hewlett Packard Enterprise 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
@ -33,8 +33,8 @@ public final class DimensionValidation {
private static final Map<String, DimensionValidator> VALIDATORS;
private static final Pattern UUID_PATTERN = Pattern
.compile("\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}");
private static final Pattern VALID_DIMENSION_NAME = Pattern.compile("[^><={}(),\"\\\\;&]+$");
private static final String INVALID_CHAR_STRING = "> < = { } ( ) \" \\ , ; &";
private static final Pattern VALID_DIMENSION_NAME = Pattern.compile("[^><={}(),\"\\\\;&\\|]+$");
private static final String INVALID_CHAR_STRING = "> < = { } ( ) \" \\ , ; & |";
private DimensionValidation() {}

View File

@ -95,9 +95,11 @@ public final class Validation {
String[] dimensionArr = Iterables.toArray(COLON_SPLITTER.split(dimensionStr), String.class);
if (dimensionArr.length == 2)
dimensions.put(dimensionArr[0], dimensionArr[1]);
if (dimensionArr.length == 1)
dimensions.put(dimensionArr[0], "");
}
DimensionValidation.validate(dimensions);
//DimensionValidation.validate(dimensions);
return dimensions;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
* Copyright (c) 2014,2016 Hewlett Packard Enterprise 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
@ -13,9 +13,13 @@
*/
package monasca.api.infrastructure.persistence;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.skife.jdbi.v2.Query;
@ -38,7 +42,14 @@ public final class DimensionQueries {
for (Iterator<Map.Entry<String, String>> it = dimensions.entrySet().iterator(); it.hasNext(); i++) {
Map.Entry<String, String> entry = it.next();
query.bind("dname" + i, entry.getKey());
query.bind("dvalue" + i, entry.getValue());
if (!Strings.isNullOrEmpty(entry.getValue())) {
List<String> values = Splitter.on('|').splitToList(entry.getValue());
int j = 0;
for (String value : values) {
query.bind("dvalue" + i + '_' + j, value);
j++;
}
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
* Copyright (c) 2014,2016 Hewlett Packard Enterprise 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
@ -13,6 +13,8 @@
*/
package monasca.api.infrastructure.persistence;
import com.google.common.base.Strings;
import java.util.Map;
/**
@ -29,11 +31,18 @@ public final class SubAlarmDefinitionQueries {
sbJoin = new StringBuilder();
for (int i = 0; i < dimensions.size(); i++) {
sbJoin.append(" inner join sub_alarm_definition_dimension d").append(i).append(" on d").append(i)
.append(".dimension_name = :dname").append(i).append(" and d").append(i)
.append(".value = :dvalue").append(i).append(" and dim.sub_alarm_definition_id = d")
int i = 0;
for (String dimension_key : dimensions.keySet()) {
sbJoin.append(" inner join sub_alarm_definition_dimension d").append(i).append(" on d")
.append(i)
.append(".dimension_name = :dname").append(i);
if (!Strings.isNullOrEmpty(dimensions.get(dimension_key))) {
sbJoin.append(" and d").append(i)
.append(".value = :dvalue").append(i);
}
sbJoin.append(" and dim.sub_alarm_definition_id = d")
.append(i).append(".sub_alarm_definition_id");
i++;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
* Copyright (c) 2015,2016 Hewlett Packard Enterprise 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
@ -13,6 +13,8 @@
*/
package monasca.api.infrastructure.persistence.influxdb;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
@ -166,8 +168,15 @@ public class InfluxV9Utils {
if (dims != null && !dims.isEmpty()) {
for (String k : dims.keySet()) {
String v = dims.get(k);
if (k != null && !k.isEmpty() && v != null && !v.isEmpty()) {
sb.append(" and \"" + sanitize(k) + "\"=" + "'" + sanitize(v) + "'");
if (k != null && !k.isEmpty()) {
sb.append(" and \"" + sanitize(k) + "\"");
if (Strings.isNullOrEmpty(v)) {
sb.append("=~ /.*/");
} else if (v.contains("|")) {
sb.append("=~ " + "/^" + sanitize(v) + "$/");
} else {
sb.append("= " + "'" + sanitize(v) + "'");
}
}
}
}

View File

@ -14,6 +14,7 @@
package monasca.api.infrastructure.persistence.mysql;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import monasca.api.domain.exception.EntityNotFoundException;
@ -95,15 +96,28 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
if (dimensions == null) {
return;
}
for (int i = 0; i < dimensions.size(); i++) {
int i = 0;
for (String dimension_key : dimensions.keySet()) {
final String indexStr = String.valueOf(i);
sbJoin.append(" inner join metric_dimension md").append(indexStr).append(" on md")
.append(indexStr)
.append(".name = :dname").append(indexStr).append(" and md").append(indexStr)
.append(".value = :dvalue").append(indexStr)
.append(" and mdd.metric_dimension_set_id = md")
.append(".name = :dname").append(indexStr);
String dim_value = dimensions.get(dimension_key);
if (!Strings.isNullOrEmpty(dim_value)) {
sbJoin.append(" and (");
List<String> values = Splitter.on('|').splitToList(dim_value);
for (int j = 0; j < values.size(); j++) {
sbJoin.append(" md").append(indexStr)
.append(".value = :dvalue").append(indexStr).append('_').append(j);
if (j < values.size() - 1) {
sbJoin.append(" or");
}
}
sbJoin.append(")");
}
sbJoin.append(" and mdd.metric_dimension_set_id = md")
.append(indexStr).append(".dimension_set_id");
i++;
}
}

View File

@ -1,6 +1,6 @@
# -*- coding: utf8 -*-
# Copyright 2014 Hewlett-Packard
# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP
# (C) Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP
# Copyright 2015 Cray Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -135,11 +135,23 @@ class MetricsRepository(metrics_repository.MetricsRepository):
sorted(dimensions.iteritems())):
# replace ' with \' to make query parsable
clean_dimension_name = dimension_name.replace("\'", "\\'")
clean_dimension_value = dimension_value.replace("\'", "\\'")
if dimension_value == "":
where_clause += " and \"{}\" =~ /.*/ ".format(
clean_dimension_name)
elif '|' in dimension_value:
# replace ' with \' to make query parsable
clean_dimension_value = dimension_value.replace("\'", "\\'")
where_clause += " and \"{}\" = '{}'".format(
clean_dimension_name.encode('utf8'),
clean_dimension_value.encode('utf8'))
where_clause += " and \"{}\" =~ /^{}$/ ".format(
clean_dimension_name.encode('utf8'),
clean_dimension_value.encode('utf8'))
else:
# replace ' with \' to make query parsable
clean_dimension_value = dimension_value.replace("\'", "\\'")
where_clause += " and \"{}\" = '{}' ".format(
clean_dimension_name.encode('utf8'),
clean_dimension_value.encode('utf8'))
if start_timestamp is not None:
where_clause += " and time > " + str(int(start_timestamp *

View File

@ -267,15 +267,27 @@ class AlarmsRepository(mysql_repository.MySQLRepository,
i = 0
for metric_dimension in query_parms['metric_dimensions']:
parsed_dimension = metric_dimension.split(':')
if len(parsed_dimension) == 1:
values = None
value_sql = ""
elif '|' in parsed_dimension[1]:
values = parsed_dimension[1].encode('utf8').split('|')
value_sql = " and ("
value_sql += " or ".join(["value = %s" for j in xrange(len(values))])
value_sql += ') '
else:
values = [parsed_dimension[1]]
value_sql = " and value = %s "
sub_select_clause += """
inner join (select distinct dimension_set_id
from metric_dimension
where name = %s and value = %s) as md{}
where name = %s {}) as md{}
on md{}.dimension_set_id = mdd.metric_dimension_set_id
""".format(i, i)
""".format(value_sql, i, i)
i += 1
sub_select_parms += [parsed_dimension[0].encode('utf8'),
parsed_dimension[1].encode('utf8')]
sub_select_parms.append(parsed_dimension[0].encode('utf8'))
if len(parsed_dimension) > 1 and values:
sub_select_parms.extend(values)
sub_select_clause += ")"
parms += sub_select_parms

View File

@ -1,4 +1,5 @@
# Copyright 2015 Cray Inc. All Rights Reserved.
# Copyright 2016 Hewlett Packard Enterprise 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
@ -77,12 +78,19 @@ class TestGetQueryDimension(unittest.TestCase):
"Dimension-2": "Value-2",
"Dimension-3": "Value-3"})
def test_malformed_dimension_no_value(self):
def test_dimension_no_value(self):
req = Mock()
req.query_string = ("foo=bar&dimensions=no_value")
req.query_string = ("foo=bar&dimensions=Dimension_no_value")
self.assertRaises(
HTTPUnprocessableEntityError, helpers.get_query_dimensions, req)
result = helpers.get_query_dimensions(req)
self.assertEqual(result, {"Dimension_no_value": ""})
def test_dimension_multi_value(self):
req = Mock()
req.query_string = ("foo=bar&dimensions=Dimension_multi_value:one|two|three")
result = helpers.get_query_dimensions(req)
self.assertEqual(result, {"Dimension_multi_value": "one|two|three"})
def test_malformed_dimension_extra_colons(self):
req = Mock()

View File

@ -1,5 +1,6 @@
# Copyright 2015 Hewlett-Packard
# Copyright 2015 Cray Inc. All Rights Reserved.
# Copyright 2016 Hewlett Packard Enterprise 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
@ -23,7 +24,7 @@ import mock
import unittest
invalid_chars = "<>={}(),\"\\;&"
invalid_chars = "<>={}(),\"\\|;&"
class TestMetricNameValidation(unittest.TestCase):

View File

@ -16,7 +16,7 @@ from monasca_api.v2.common.exceptions import HTTPUnprocessableEntityError
import re
invalid_chars = "<>={}(),'\"\\\\;&"
invalid_chars = "<>={}(),\"\\\\|;&"
restricted_chars = re.compile('[' + invalid_chars + ']')

View File

@ -128,6 +128,7 @@ class Alarms(alarms_api_v2.AlarmsV2API,
# ensure metric_dimensions is a list
if 'metric_dimensions' in query_parms and isinstance(query_parms['metric_dimensions'], str):
query_parms['metric_dimensions'] = query_parms['metric_dimensions'].split(',')
self._validate_dimensions(query_parms['metric_dimensions'])
offset = helpers.get_query_param(req, 'offset')
if offset is not None and not isinstance(offset, int):
@ -152,6 +153,20 @@ class Alarms(alarms_api_v2.AlarmsV2API,
res.body = helpers.dumpit_utf8(result)
res.status = falcon.HTTP_200
@staticmethod
def _validate_dimensions(dimensions):
try:
assert isinstance(dimensions, list)
for dimension in dimensions:
name_value = dimension.split('=')
validation.dimension_key(name_value[0])
if len(name_value) > 1 and '|' in name_value[1]:
values = name_value[1].split('|')
for value in values:
validation.dimension_value(value)
except Exception as e:
raise HTTPUnprocessableEntityError("Unprocessable Entity", e.message)
@resource.resource_try_catch_block
def _alarm_update(self, tenant_id, alarm_id, new_state, lifecycle_state,
link):

View File

@ -184,6 +184,8 @@ def get_query_dimensions(req):
if len(dimension_name_value) == 2:
dimensions[dimension_name_value[0]] = dimension_name_value[
1]
elif len(dimension_name_value) == 1:
dimensions[dimension_name_value[0]] = ""
else:
raise Exception('Dimensions are malformed')
return dimensions

View File

@ -89,6 +89,96 @@ class TestAlarms(base.BaseMonascaTest):
self.assertEqual(alarm_definition_ids[0],
element['alarm_definition']['id'])
@test.attr(type="gate")
def test_list_alarms_by_metric_dimensions_no_value(self):
metric_name = data_utils.rand_name('metric')
match_by_key = data_utils.rand_name('key')
dim_key = data_utils.rand_name('key')
alarm_def = helpers.create_alarm_definition(
name=data_utils.rand_name('definition'),
expression=metric_name + " > 1",
match_by=[match_by_key])
metric_1 = helpers.create_metric(metric_name,
{match_by_key: data_utils.rand_name('value'),
dim_key: data_utils.rand_name('value')})
metric_2 = helpers.create_metric(metric_name,
{match_by_key: data_utils.rand_name('value'),
dim_key: data_utils.rand_name('value')})
metric_3 = helpers.create_metric(metric_name,
{match_by_key: data_utils.rand_name('value')})
metrics = [metric_1, metric_2, metric_3]
resp, response_body = self.monasca_client.create_alarm_definitions(alarm_def)
self.assertEqual(201, resp.status)
for i in xrange(constants.MAX_RETRIES):
resp, alarm_def_result = self.monasca_client.create_metrics(metrics)
self.assertEqual(204, resp.status)
resp, response_body = self.monasca_client.list_alarms('?metric_name=' + metric_name)
self.assertEqual(200, resp.status)
if len(response_body['elements']) >= 3:
break
time.sleep(constants.RETRY_WAIT_SECS)
if i >= constants.MAX_RETRIES - 1:
self.fail("Timeout creating alarms, required 3 but found {}".format(
len(response_body['elements'])))
query_parms = '?metric_dimensions=' + dim_key
resp, response_body = self.monasca_client.list_alarms(query_parms)
self._verify_list_alarms_elements(resp, response_body,
expect_num_elements=2)
dimension_sets = []
for element in response_body['elements']:
self.assertEqual(metric_name, element['metrics'][0]['name'])
dimension_sets.append(element['metrics'][0]['dimensions'])
self.assertIn(metric_1['dimensions'], dimension_sets)
self.assertIn(metric_2['dimensions'], dimension_sets)
self.assertNotIn(metric_3['dimensions'], dimension_sets)
@test.attr(type="gate")
def test_list_alarms_by_metric_dimensions_multi_value(self):
metric_name = data_utils.rand_name('metric')
match_by_key = data_utils.rand_name('key')
dim_key = data_utils.rand_name('key')
dim_value_1 = data_utils.rand_name('value')
dim_value_2 = data_utils.rand_name('value')
alarm_def = helpers.create_alarm_definition(
name=data_utils.rand_name('definition'),
expression=metric_name + " > 1",
match_by=[match_by_key])
metric_1 = helpers.create_metric(metric_name, {match_by_key: data_utils.rand_name('value'),
dim_key: dim_value_1})
metric_2 = helpers.create_metric(metric_name, {match_by_key: data_utils.rand_name('value'),
dim_key: dim_value_2})
metric_3 = helpers.create_metric(metric_name, {match_by_key: data_utils.rand_name('value')})
metrics = [metric_1, metric_2, metric_3]
resp, response_body = self.monasca_client.create_alarm_definitions(alarm_def)
self.assertEqual(201, resp.status)
for i in xrange(constants.MAX_RETRIES):
resp, alarm_def_result = self.monasca_client.create_metrics(metrics)
self.assertEqual(204, resp.status)
resp, response_body = self.monasca_client.list_alarms('?metric_name=' + metric_name)
self.assertEqual(200, resp.status)
if len(response_body['elements']) >= 3:
return
time.sleep(constants.RETRY_WAIT_SECS)
if i >= constants.MAX_RETRIES - 1:
self.fail("Timeout creating alarms, required 3 but found {}".format(
len(response_body['elements'])))
query_parms = '?metric_dimensions=' + dim_key + ':' + dim_value_1 + '|' + dim_value_2
resp, response_body = self.monasca_client.list_alarms(query_parms)
self._verify_list_alarms_elements(resp, response_body,
expect_num_elements=2)
dimension_sets = []
for element in response_body['elements']:
self.assertEqual(metric_name, element['metrics'][0]['name'])
dimension_sets.append(element['metrics'][0]['dimensions'])
self.assertIn(metric_1['dimensions'], dimension_sets)
self.assertIn(metric_2['dimensions'], dimension_sets)
self.assertNotIn(metric_3['dimensions'], dimension_sets)
@test.attr(type="gate")
def test_list_alarms_by_state(self):
helpers.delete_alarm_definitions(self.monasca_client)

View File

@ -1,4 +1,4 @@
# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP
# (C) Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP
#
# 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
@ -290,6 +290,74 @@ class TestMetrics(base.BaseMonascaTest):
"metrics = 0"
self.fail(error_msg)
@test.attr(type='gate')
def test_list_metrics_dimension_query_multi_value(self):
name = data_utils.rand_name('name')
key_service = "service"
value_1 = data_utils.rand_name('value')
value_2 = data_utils.rand_name('value')
metric_1 = helpers.create_metric(name, {key_service: value_1})
metric_2 = helpers.create_metric(name, {key_service: value_2})
metric_3 = helpers.create_metric(name)
metrics = [metric_1, metric_2, metric_3]
resp, response_body = self.monasca_client.create_metrics(metrics)
self.assertEqual(204, resp.status)
query_param = '?name=' + name + '&dimensions=service:' + value_1 + '|' + value_2
for i in xrange(constants.MAX_RETRIES):
resp, response_body = self.monasca_client.list_metrics(query_param)
self.assertEqual(200, resp.status)
elements = response_body['elements']
if len(elements) == 2:
dimension_sets = []
for element in elements:
self.assertEqual(name, element['name'])
dimension_sets.append(element['dimensions'])
self.assertIn(metric_1['dimensions'], dimension_sets)
self.assertIn(metric_2['dimensions'], dimension_sets)
self.assertNotIn(metric_3['dimensions'], dimension_sets)
return
time.sleep(constants.RETRY_WAIT_SECS)
if i == constants.MAX_RETRIES - 1:
error_msg = "Timeout on waiting for metrics: at least " \
"2 metrics are needed. Current number of " \
"metrics = 0"
self.fail(error_msg)
@test.attr(type='gate')
def test_list_metrics_dimension_query_no_value(self):
name = data_utils.rand_name('name')
key_service = "service"
value_1 = data_utils.rand_name('value')
value_2 = data_utils.rand_name('value')
metric_1 = helpers.create_metric(name, {key_service: value_1})
metric_2 = helpers.create_metric(name, {key_service: value_2})
metric_3 = helpers.create_metric(name)
metrics = [metric_1, metric_2, metric_3]
resp, response_body = self.monasca_client.create_metrics(metrics)
self.assertEqual(204, resp.status)
query_param = '?name=' + name + '&dimensions=service'
for i in xrange(constants.MAX_RETRIES):
resp, response_body = self.monasca_client.list_metrics(query_param)
self.assertEqual(200, resp.status)
elements = response_body['elements']
if len(elements) == 2:
dimension_sets = []
for element in elements:
self.assertEqual(name, element['name'])
dimension_sets.append(element['dimensions'])
self.assertIn(metric_1['dimensions'], dimension_sets)
self.assertIn(metric_2['dimensions'], dimension_sets)
self.assertNotIn(metric_3['dimensions'], dimension_sets)
return
time.sleep(constants.RETRY_WAIT_SECS)
if i == constants.MAX_RETRIES - 1:
error_msg = "Timeout on waiting for metrics: at least " \
"2 metrics are needed. Current number of " \
"metrics = 0"
self.fail(error_msg)
@test.attr(type='gate')
def test_list_metrics_with_name(self):
name = data_utils.rand_name('name')