# -*- coding: utf-8 -*-
# (C) Copyright 2014-2016 Hewlett Packard Enterprise Development 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
# 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 the License.

# TODO(RMH): Check if ' should be added in the list of INVALID_CHARS.
# TODO(RMH): test_create_metric_no_value, should return 422 if value not sent
import time
from urllib import parse as urlparse

from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions

from monasca_tempest_tests.tests.api import base
from monasca_tempest_tests.tests.api import constants
from monasca_tempest_tests.tests.api import helpers


class TestMetrics(base.BaseMonascaTest):

    @decorators.attr(type=["gate", "smoke"])
    def test_create_metric(self):
        name = data_utils.rand_name(u'name')
        key = data_utils.rand_name(u'key')
        value = data_utils.rand_name(u'value')
        timestamp = int(round(time.time() * 1000))
        time_iso = helpers.timestamp_to_iso(timestamp)
        end_timestamp = int(round((time.time() + 3600 * 24) * 1000))
        end_time_iso = helpers.timestamp_to_iso(end_timestamp)
        value_meta_key = data_utils.rand_name(u'value_meta_key')
        value_meta_value = data_utils.rand_name(u'value_meta_value')
        metric = helpers.create_metric(name=name,
                                       dimensions={key: value},
                                       timestamp=timestamp,
                                       value=1.23,
                                       value_meta={
                                           value_meta_key: value_meta_value
                                       })
        resp, response_body = self.monasca_client.create_metrics(metric)
        self.assertEqual(204, resp.status)
        query_param = '?name=' + name + '&start_time=' + time_iso + \
                      '&end_time=' + end_time_iso
        for i in range(constants.MAX_RETRIES):
            resp, response_body = self.monasca_client.\
                list_measurements(query_param)
            self.assertEqual(200, resp.status)
            elements = response_body['elements']
            for element in elements:
                # check if metric is there and dimension info already available
                if element['name'] == name and len(element['dimensions']) > 0:
                    self._verify_list_measurements_element(element, key, value)
                    measurement = element['measurements'][0]
                    self._verify_list_measurements_measurement(
                        measurement, metric, value_meta_key, value_meta_value)
                    return
            time.sleep(constants.RETRY_WAIT_SECS)
            if i == constants.MAX_RETRIES - 1:
                error_msg = "Failed test_create_metric: " \
                            "timeout on waiting for metrics: at least " \
                            "one metric is needed. Current number of " \
                            "metrics = 0"
                self.fail(error_msg)

    @decorators.attr(type='gate')
    def test_create_metric_with_multibyte_character(self):
        name = data_utils.rand_name(u'ｎａｍｅ')
        key = data_utils.rand_name(u'ｋｅｙ')
        value = data_utils.rand_name(u'ｖａｌｕｅ')
        timestamp = int(round(time.time() * 1000))
        time_iso = helpers.timestamp_to_iso(timestamp)
        end_timestamp = int(round((time.time() + 3600 * 24) * 1000))
        end_time_iso = helpers.timestamp_to_iso(end_timestamp)
        value_meta_key = data_utils.rand_name(u'value_meta_ｋｅｙ')
        value_meta_value = data_utils.rand_name(u'value_meta_ｖａｌｕｅ')
        metric = helpers.create_metric(name=name,
                                       dimensions={key: value},
                                       timestamp=timestamp,
                                       value=1.23,
                                       value_meta={
                                           value_meta_key: value_meta_value
                                       })
        resp, response_body = self.monasca_client.create_metrics(metric)
        self.assertEqual(204, resp.status)
        query_param = '?name=' + urlparse.quote(name.encode('utf8')) + \
                      '&start_time=' + time_iso + '&end_time=' + end_time_iso
        for i in range(constants.MAX_RETRIES):
            resp, response_body = self.monasca_client.\
                list_measurements(query_param)
            self.assertEqual(200, resp.status)
            elements = response_body['elements']
            for element in elements:
                if element['name'] == name:
                    self._verify_list_measurements_element(element, key, value)
                    measurement = element['measurements'][0]
                    self._verify_list_measurements_measurement(
                        measurement, metric, value_meta_key, value_meta_value)
                    return
            time.sleep(constants.RETRY_WAIT_SECS)
            if i == constants.MAX_RETRIES - 1:
                error_msg = "Failed test_create_metric: " \
                            "timeout on waiting for metrics: at least " \
                            "one metric is needed. Current number of " \
                            "metrics = 0"
                self.fail(error_msg)

    @decorators.attr(type='gate')
    def test_create_metrics(self):
        name = data_utils.rand_name(u'name')
        key = data_utils.rand_name(u'key')
        value = data_utils.rand_name(u'value')
        timestamp = int(round(time.time() * 1000))
        time_iso = helpers.timestamp_to_iso(timestamp)
        end_timestamp = int(round(timestamp + 3600 * 24 * 1000))
        end_time_iso = helpers.timestamp_to_iso(end_timestamp)
        value_meta_key1 = data_utils.rand_name(u'meta_key')
        value_meta_value1 = data_utils.rand_name(u'meta_value')
        value_meta_key2 = data_utils.rand_name(u'value_meta_key')
        value_meta_value2 = data_utils.rand_name(u'value_meta_value')
        metrics = [
            helpers.create_metric(name=name,
                                  dimensions={key: value},
                                  timestamp=timestamp,
                                  value=1.23,
                                  value_meta={
                                      value_meta_key1: value_meta_value1
                                  }),
            helpers.create_metric(name=name,
                                  dimensions={key: value},
                                  timestamp=timestamp + 6000,
                                  value=4.56,
                                  value_meta={
                                      value_meta_key2: value_meta_value2
                                  })
        ]
        resp, response_body = self.monasca_client.create_metrics(metrics)
        self.assertEqual(204, resp.status)
        query_param = '?name=' + name + '&start_time=' + str(time_iso) + \
                      '&end_time=' + str(end_time_iso)
        for i in range(constants.MAX_RETRIES):
            resp, response_body = self.monasca_client.\
                list_measurements(query_param)
            self.assertEqual(200, resp.status)
            elements = response_body['elements']
            for element in elements:
                if (element['name'] == name and
                        len(element['measurements']) == 2):
                    self._verify_list_measurements_element(element, key, value)
                    first_measurement = element['measurements'][0]
                    second_measurement = element['measurements'][1]
                    self._verify_list_measurements_measurement(
                        first_measurement, metrics[0], value_meta_key1,
                        value_meta_value1)
                    self._verify_list_measurements_measurement(
                        second_measurement, metrics[1], value_meta_key2,
                        value_meta_value2)
                    return
            time.sleep(constants.RETRY_WAIT_SECS)
            if i == constants.MAX_RETRIES - 1:
                error_msg = "Failed test_create_metrics: " \
                            "timeout on waiting for metrics: at least " \
                            "one metric is needed. Current number of " \
                            "metrics = 0"
                self.fail(error_msg)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_with_no_name(self):
        metric = helpers.create_metric(name=None)
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.create_metrics,
                          metric)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_with_empty_name(self):
        metric = helpers.create_metric(name='')
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.create_metrics,
                          metric)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_with_empty_value_in_dimensions(self):
        name = data_utils.rand_name(u'name')
        metric = helpers.create_metric(name=name,
                                       dimensions={u'key': ''})
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.create_metrics,
                          metric)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_with_empty_key_in_dimensions(self):
        name = data_utils.rand_name(u'name')
        metric = helpers.create_metric(name=name,
                                       dimensions={'': u'value'})
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.create_metrics,
                          metric)

    @decorators.attr(type='gate')
    def test_create_metric_with_no_dimensions(self):
        name = data_utils.rand_name(u'name')
        timestamp = int(round(time.time() * 1000))
        time_iso = helpers.timestamp_to_iso(timestamp)
        end_timestamp = int(round(timestamp + 3600 * 24 * 1000))
        end_time_iso = helpers.timestamp_to_iso(end_timestamp)
        value_meta_key = data_utils.rand_name(u'value_meta_key')
        value_meta_value = data_utils.rand_name(u'value_meta_value')
        metric = helpers.create_metric(name=name,
                                       dimensions=None,
                                       timestamp=timestamp,
                                       value=1.23,
                                       value_meta={
                                           value_meta_key: value_meta_value})
        resp, response_body = self.monasca_client.create_metrics(metric)
        self.assertEqual(204, resp.status)
        query_param = '?name=' + str(name) + '&start_time=' + str(time_iso) \
                      + '&end_time=' + str(end_time_iso)
        for i in range(constants.MAX_RETRIES):
            resp, response_body = self.monasca_client.\
                list_measurements(query_param)
            self.assertEqual(200, resp.status)
            elements = response_body['elements']
            for element in elements:
                if element['name'] == name:
                    self._verify_list_measurements_element(
                        element, test_key=None, test_value=None)
                    if len(element['measurements']) > 0:
                        measurement = element['measurements'][0]
                        self._verify_list_measurements_measurement(
                            measurement, metric, value_meta_key,
                            value_meta_value)
                        return
            time.sleep(constants.RETRY_WAIT_SECS)
            if i == constants.MAX_RETRIES - 1:
                error_msg = "Failed test_create_metric_with_no_dimensions: " \
                            "timeout on waiting for metrics: at least " \
                            "one metric is needed. Current number of " \
                            "metrics = 0"
                self.fail(error_msg)

    @decorators.attr(type='gate')
    def test_create_metric_with_colon_in_dimension_value(self):
        name = data_utils.rand_name(u'name')
        key = u'url'
        value = u'http://localhost:8070/v2.0'
        timestamp = int(round(time.time() * 1000))
        time_iso = helpers.timestamp_to_iso(timestamp)
        end_timestamp = int(round((time.time() + 3600 * 24) * 1000))
        end_time_iso = helpers.timestamp_to_iso(end_timestamp)
        metric = helpers.create_metric(name=name,
                                       dimensions={key: value})
        resp, response_body = self.monasca_client.create_metrics(metric)
        self.assertEqual(204, resp.status)
        query_param = '?name=' + name + '&start_time=' + time_iso + \
                      '&end_time=' + end_time_iso + \
                      '&dimensions=' + key + ':' + value
        for i in range(constants.MAX_RETRIES):
            resp, response_body = self.monasca_client. \
                list_measurements(query_param)
            self.assertEqual(200, resp.status)
            elements = response_body['elements']
            for element in elements:
                if element['name'] == name:
                    self._verify_list_measurements_element(element, key, value)
                    measurement = element['measurements'][0]
                    self._verify_list_measurements_measurement(
                        measurement, metric, None, None)
                    return
            time.sleep(constants.RETRY_WAIT_SECS)
            if i == constants.MAX_RETRIES - 1:
                error_msg = "Failed test_create_metric: " \
                            "timeout on waiting for metrics: at least " \
                            "one metric is needed. Current number of " \
                            "metrics = 0"
                self.fail(error_msg)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_with_no_timestamp(self):
        metric = helpers.create_metric()
        metric['timestamp'] = None
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.create_metrics,
                          metric)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_no_value(self):
        timestamp = int(round(time.time() * 1000))
        metric = helpers.create_metric(timestamp=timestamp,
                                       value=None)
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.create_metrics,
                          metric)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_with_name_exceeds_max_length(self):
        long_name = u"x" * (constants.MAX_METRIC_NAME_LENGTH + 1)
        metric = helpers.create_metric(long_name)
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.create_metrics,
                          metric)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_with_invalid_chars_in_name(self):
        for invalid_char in constants.INVALID_NAME_CHARS:
            metric = helpers.create_metric(invalid_char)
            self.assertRaises(exceptions.UnprocessableEntity,
                              self.monasca_client.create_metrics,
                              metric)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_with_invalid_chars_in_dimensions(self):
        for invalid_char in constants.INVALID_DIMENSION_CHARS:
            metric = helpers.create_metric(u'name-1', {u'key-1': invalid_char})
            self.assertRaises(exceptions.UnprocessableEntity,
                              self.monasca_client.create_metrics,
                              metric)
        for invalid_char in constants.INVALID_DIMENSION_CHARS:
            metric = helpers.create_metric(u'name-1',
                                           {invalid_char: u'value-1'})
            self.assertRaises(exceptions.UnprocessableEntity,
                              self.monasca_client.create_metrics,
                              metric)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_dimension_key_exceeds_max_length(self):
        long_key = u"x" * (constants.MAX_DIMENSION_KEY_LENGTH + 1)
        metric = helpers.create_metric(u'name-1', {long_key: u'value-1'})
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.create_metrics,
                          metric)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_dimension_value_exceeds_max_length(self):
        long_value = u"x" * (constants.MAX_DIMENSION_VALUE_LENGTH + 1)
        metric = helpers.create_metric(u'name-1', {u'key-1': long_value})
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.create_metrics,
                          metric)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_with_value_meta_name_exceeds_max_length(self):
        long_value_meta_name = u"x" * (constants.MAX_VALUE_META_NAME_LENGTH + 1)
        value_meta_dict = {long_value_meta_name: u"value_meta_value"}
        metric = helpers.create_metric(name=u'name', value_meta=value_meta_dict)
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.create_metrics,
                          metric)

    @decorators.attr(type='gate')
    @decorators.attr(type=['negative'])
    def test_create_metric_with_value_meta_exceeds_max_length(self):
        value_meta_name = u"x"
        long_value_meta_value = u"y" * constants.MAX_VALUE_META_TOTAL_LENGTH
        value_meta_dict = {value_meta_name: long_value_meta_value}
        metric = helpers.create_metric(name=u'name', value_meta=value_meta_dict)
        self.assertRaises(exceptions.UnprocessableEntity,
                          self.monasca_client.create_metrics,
                          metric)

    @decorators.attr(type=["gate", "smoke"])
    def test_list_metrics(self):
        resp, response_body = self.monasca_client.list_metrics()
        self.assertEqual(200, resp.status)
        self.assertTrue(set(['links', 'elements']) == set(response_body))
        elements = response_body['elements']
        element = elements[0]
        self._verify_list_metrics_element(element, test_key=None,
                                          test_value=None, test_name=None)
        self.assertTrue(set(['id', 'name', 'dimensions']) == set(element))

    @decorators.attr(type='gate')
    def test_list_metrics_with_dimensions(self):
        name = data_utils.rand_name(u'name')
        key = data_utils.rand_name(u'key')
        value = data_utils.rand_name(u'value')
        metric = helpers.create_metric(name=name, dimensions={key: value})
        resp, response_body = self.monasca_client.create_metrics(metric)
        self.assertEqual(204, resp.status)
        query_param = '?dimensions=' + key + ':' + value
        for i in range(constants.MAX_RETRIES):
            resp, response_body = self.monasca_client.list_metrics(query_param)
            self.assertEqual(200, resp.status)
            elements = response_body['elements']
            for element in elements:
                if element['dimensions'][key] == value:
                    self._verify_list_metrics_element(element, test_name=name)
                    return
            time.sleep(constants.RETRY_WAIT_SECS)
            if i == constants.MAX_RETRIES - 1:
                error_msg = "Failed test_list_metrics_with_dimensions: " \
                            "timeout on waiting for metrics: at least " \
                            "one metric is needed. Current number of " \
                            "metrics = 0"
                self.fail(error_msg)

    @decorators.attr(type='gate')
    def test_list_metrics_dimension_query_multi_value_with_diff_names(self):
        metrics, name, key_service, values = \
            self._create_metrics_with_different_dimensions(same_name=False)
        metric_dimensions = self._get_metric_dimensions(
            key_service, values, same_metric_name=False)
        query_param = '?dimensions=' + key_service + ':' + values[0] + '|' +\
                      values[1]
        self._verify_dimensions(query_param, metric_dimensions)

    @decorators.attr(type='gate')
    def test_list_metrics_dimension_query_no_value_with_diff_names(self):
        metrics, name, key_service, values = \
            self._create_metrics_with_different_dimensions(same_name=False)
        metric_dimensions = self._get_metric_dimensions(
            key_service, values, same_metric_name=False)
        query_param = '?dimensions=' + key_service
        self._verify_dimensions(query_param, metric_dimensions)

    @decorators.attr(type='gate')
    def test_list_metrics_dimension_query_multi_value_with_same_name(self):
        # Skip the test for now due to InfluxDB Inconsistency
        return
        metrics, name, key_service, values = \
            self._create_metrics_with_different_dimensions(same_name=True)
        metric_dimensions = self._get_metric_dimensions(
            key_service, values, same_metric_name=True)
        query_param = '?name=' + name + '&dimensions=' + key_service + ':' +\
                      values[0] + '|' + values[1]
        self._verify_dimensions(query_param, metric_dimensions)

    @decorators.attr(type='gate')
    def test_list_metrics_dimension_query_no_value_with_same_name(self):
        # Skip the test for now due to InfluxDB Inconsistency
        return
        metrics, name, key_service, values = \
            self._create_metrics_with_different_dimensions(same_name=True)
        metric_dimensions = self._get_metric_dimensions(
            key_service, values, same_metric_name=True)
        query_param = '?name=' + name + '&dimensions=' + key_service
        self._verify_dimensions(query_param, metric_dimensions)

    @decorators.attr(type='gate')
    def test_list_metrics_with_name(self):
        name = data_utils.rand_name(u'name')
        key = data_utils.rand_name(u'key')
        value = data_utils.rand_name(u'value')
        metric = helpers.create_metric(name=name,
                                       dimensions={key: value})
        resp, response_body = self.monasca_client.create_metrics(metric)
        self.assertEqual(204, resp.status)
        query_param = '?name=' + str(name)
        for i in range(constants.MAX_RETRIES):
            resp, response_body = self.monasca_client.list_metrics(query_param)
            self.assertEqual(200, resp.status)
            elements = response_body['elements']
            for element in elements:
                if element['name'] == name:
                    self._verify_list_metrics_element(element, test_key=key,
                                                      test_value=value)
                    return
            time.sleep(constants.RETRY_WAIT_SECS)
            if i == constants.MAX_RETRIES - 1:
                error_msg = "Failed test_list_metrics_with_name: " \
                            "timeout on waiting for metrics: at least " \
                            "one metric is needed. Current number of " \
                            "metrics = 0"
                self.fail(error_msg)

    @decorators.attr(type='gate')
    def test_list_metrics_with_project(self):
        name = data_utils.rand_name(u'name')
        key = data_utils.rand_name(u'key')
        value = data_utils.rand_name(u'value')
        project = self.projects_client.create_project(
            name=data_utils.rand_name(u'test_project'))['project']
        # Delete the project at the end of the test
        self.addCleanup(self.projects_client.delete_project, project['id'])
        metric = helpers.create_metric(name=name,
                                       dimensions={key: value})
        resp, response_body = self.monasca_client.create_metrics(
            metric, tenant_id=project['id'])
        self.assertEqual(204, resp.status)
        query_param = '?tenant_id=' + str(project['id'])
        for i in range(constants.MAX_RETRIES):
            resp, response_body = self.monasca_client.list_metrics(query_param)
            self.assertEqual(200, resp.status)
            elements = response_body['elements']
            for element in elements:
                if element['name'] == name:
                    self._verify_list_metrics_element(element, test_key=key,
                                                      test_value=value)
                    return
            time.sleep(constants.RETRY_WAIT_SECS)
            if i == constants.MAX_RETRIES - 1:
                error_msg = "Failed test_list_metrics_with_tenant: " \
                            "timeout on waiting for metrics: at least " \
                            "one metric is needed. Current number of " \
                            "metrics = 0"
                self.fail(error_msg)

    @decorators.attr(type='gate')
    def test_list_metrics_with_offset_limit(self):
        name = data_utils.rand_name()
        key1 = data_utils.rand_name()
        key2 = data_utils.rand_name()

        metrics = [
            helpers.create_metric(name=name, dimensions={
                key1: u'value-1', key2: u'value-1'}),
            helpers.create_metric(name=name, dimensions={
                key1: u'value-2', key2: u'value-2'}),
            helpers.create_metric(name=name, dimensions={
                key1: u'value-3', key2: u'value-3'}),
            helpers.create_metric(name=name, dimensions={
                key1: u'value-4', key2: u'value-4'})
        ]
        self.monasca_client.create_metrics(metrics)
        query_param = '?name=' + name
        for i in range(constants.MAX_RETRIES):
            resp, response_body = self.monasca_client.list_metrics(query_param)
            elements = response_body['elements']
            if elements and len(elements) == 4:
                break
            time.sleep(constants.RETRY_WAIT_SECS)
            if i == constants.MAX_RETRIES - 1:
                error_msg = ("Failed test_list_metrics_with_offset_limit: "
                             "timeout on waiting for metrics: 4 metrics "
                             "are needed. Current number of elements = "
                             "{}").format(len(elements))
                self.fail(error_msg)

        first_element = elements[0]
        query_parms = '?name=' + name + '&limit=4'
        resp, response_body = self.monasca_client.list_metrics(query_parms)
        self.assertEqual(200, resp.status)
        elements = response_body['elements']
        self.assertEqual(4, len(elements))
        self.assertEqual(first_element, elements[0])

        for metric_index in range(len(elements) - 1):
            metric = elements[metric_index]
            max_limit = 3 - metric_index

            for limit in range(1, max_limit):
                first_index = metric_index + 1
                last_index = first_index + limit
                expected_elements = elements[first_index:last_index]

                query_parms = '?name=' + name + '&offset=' + \
                              str(metric['id']) + '&limit=' + \
                              str(limit)
                resp, response_body = self.\
                    monasca_client.list_metrics(query_parms)
                self.assertEqual(200, resp.status)
                new_elements = response_body['elements']

                self.assertEqual(limit, len(new_elements))
                for i in range(len(expected_elements)):
                    self.assertEqual(expected_elements[i], new_elements[i])

    def _verify_list_measurements_element(self, element, test_key, test_value):
        self.assertEqual(set(element),
                         set(['columns', 'dimensions', 'id', 'measurements',
                             'name']))
        self.assertEqual(set(element['columns']),
                         set(['timestamp', 'value', 'value_meta']))
        self.assertTrue(str(element['id']) is not None)
        if test_key is not None and test_value is not None:
            self.assertEqual(
                element['dimensions'][test_key].encode('utf-8'),
                test_value.encode('utf-8')
            )

    def _verify_list_measurements_measurement(self, measurement,
                                              test_metric, test_vm_key,
                                              test_vm_value):
        # Timestamps stored in influx sometimes are 1 millisecond different to
        # the value stored by the persister. Check if the timestamps are
        # equal in one millisecond range to pass the test.
        time_iso_millis = helpers.timestamp_to_iso_millis(
            test_metric['timestamp'] + 0)
        time_iso_millis_plus = helpers.timestamp_to_iso_millis(
            test_metric['timestamp'] + 1)
        time_iso_millis_minus = helpers.timestamp_to_iso_millis(
            test_metric['timestamp'] - 1)
        if str(measurement[0]) != time_iso_millis and str(measurement[0]) != \
                time_iso_millis_plus and str(measurement[0]) != \
                time_iso_millis_minus:
            error_msg = ("Mismatch Error: None of {}, {}, {} matches {}").\
                format(time_iso_millis, time_iso_millis_plus,
                       time_iso_millis_minus, str(measurement[0]))
            self.fail(error_msg)
        self.assertEqual(measurement[1], test_metric['value'])
        if test_vm_key is not None and test_vm_value is not None:
            self.assertEqual(
                measurement[2][test_vm_key].encode('utf-8'),
                test_vm_value.encode('utf-8')
            )

    def _verify_list_metrics_element(self, element, test_key=None,
                                     test_value=None, test_name=None):
        self.assertTrue(type(element['id']) is str)
        self.assertTrue(type(element['name']) is str)
        self.assertTrue(type(element['dimensions']) is dict)
        self.assertEqual(set(element), set(['dimensions', 'id', 'name']))
        self.assertTrue(str(element['id']) is not None)
        if test_key is not None and test_value is not None:
            self.assertEqual(element['dimensions'][test_key], test_value)
        if test_name is not None:
            self.assertEqual(element['name'], test_name)

    @decorators.attr(type='gate')
    def test_list_metrics_with_time_args(self):
        name = data_utils.rand_name(u'name')
        key = data_utils.rand_name(u'key')
        value_org = data_utils.rand_name(u'value')

        now = int(round(time.time() * 1000))
        #
        # Built start and end time args before and after the measurement.
        #
        start_iso = helpers.timestamp_to_iso(now - 1000)
        end_timestamp = int(round(now + 1000))
        end_iso = helpers.timestamp_to_iso(end_timestamp)

        metric = helpers.create_metric(name=name,
                                       dimensions={key: value_org},
                                       timestamp=now)

        self.monasca_client.create_metrics(metric)
        for timer in range(constants.MAX_RETRIES):
            query_parms = '?name=' + name + '&start_time=' + start_iso + '&end_time=' + end_iso
            resp, response_body = self.monasca_client.list_metrics(query_parms)
            self.assertEqual(200, resp.status)
            elements = response_body['elements']
            if elements:
                dimensions = elements[0]
                dimension = dimensions['dimensions']
                value = dimension[str(key)]
                self.assertEqual(value_org, value)
                break
            else:
                time.sleep(constants.RETRY_WAIT_SECS)
                if timer == constants.MAX_RETRIES - 1:
                    skip_msg = "Skipped test_list_metrics_with_time_args: " \
                               "timeout on waiting for metrics: at least one " \
                               "metric is needed. Current number of metrics " \
                               "= 0"
                    raise self.skipException(skip_msg)

    @staticmethod
    def _get_metric_dimensions(key_service, values, same_metric_name):
        if same_metric_name:
            metric_dimensions = [{key_service: values[0], 'key3': ''},
                                 {key_service: values[1], 'key3': ''},
                                 {key_service: '', 'key3': 'value3'}]
        else:
            metric_dimensions = [{key_service: values[0]},
                                 {key_service: values[1]},
                                 {'key3': 'value3'}]
        return metric_dimensions

    def _verify_dimensions(self, query_param, metric_dimensions):
        for i in range(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:
                    dimension_sets.append(element['dimensions'])
                self.assertIn(metric_dimensions[0], dimension_sets)
                self.assertIn(metric_dimensions[1], dimension_sets)
                self.assertNotIn(metric_dimensions[2], 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 = {}".format(len(elements))
                self.fail(error_msg)

    def _create_metrics_with_different_dimensions(self, same_name=True):
        name1 = data_utils.rand_name(u'name1')
        name2 = name1 if same_name else data_utils.rand_name(u'name2')
        name3 = name1 if same_name else data_utils.rand_name(u'name3')
        key_service = data_utils.rand_name(u'service')
        values = [data_utils.rand_name(u'value1'),
                  data_utils.rand_name(u'value2')]
        metrics = [helpers.create_metric(name1, {key_service: values[0]}),
                   helpers.create_metric(name2, {key_service: values[1]}),
                   helpers.create_metric(name3, {u'key3': u'value3'})]
        resp, response_body = self.monasca_client.create_metrics(metrics)
        self.assertEqual(204, resp.status)
        return metrics, name1, key_service, values
