<filename>monasca_api/v2/reference/helpers.py
# Copyright 2014 Hewlett-Packard
# Copyright 2015 Cray Inc. All Rights Reserved.
#
# 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.

import datetime
import json
import urllib
import urlparse

import falcon
from oslo_log import log
from oslo_utils import timeutils
import simplejson

from monasca_api.common.repositories import constants
from monasca_api.v2.common.exceptions import HTTPUnprocessableEntityError
from monasca_api.v2.common.schemas import dimensions_schema
from monasca_api.v2.common.schemas import exceptions as schemas_exceptions
from monasca_api.v2.common.schemas import metric_name_schema

LOG = log.getLogger(__name__)


def read_json_msg_body(req):
    """Read the json_msg from the http request body and return them as JSON.

    :param req: HTTP request object.
    :return: Returns the metrics as a JSON object.
    :raises falcon.HTTPBadRequest:
    """
    try:
        msg = req.stream.read()
        json_msg = json.loads(msg)
        return json_msg
    except ValueError as ex:
        LOG.debug(ex)
        raise falcon.HTTPBadRequest('Bad request',
                                    'Request body is not valid JSON')


def validate_json_content_type(req):
    if req.content_type not in ['application/json']:
        raise falcon.HTTPBadRequest('Bad request', 'Bad content type. Must be '
                                                   'application/json')


def is_in_role(req, authorized_roles):
    """Is one or more of the X-ROLES in the supplied authorized_roles.

    :param req: HTTP request object. Must contain "X-ROLES" in the HTTP
    request header.
    :param authorized_roles: List of authorized roles to check against.
    :return: Returns True if in the list of authorized roles, otherwise False.
    """
    str_roles = req.get_header('X-ROLES')
    if str_roles is None:
        return False
    roles = str_roles.lower().split(',')
    for role in roles:
        if role in authorized_roles:
            return True
    return False


def validate_authorization(req, authorized_roles):
    """Validates whether one or more X-ROLES in the HTTP header is authorized.

    :param req: HTTP request object. Must contain "X-ROLES" in the HTTP
    request header.
    :param authorized_roles: List of authorized roles to check against.
    :raises falcon.HTTPUnauthorized
    """
    str_roles = req.get_header('X-ROLES')
    if str_roles is None:
        raise falcon.HTTPUnauthorized('Forbidden',
                                      'Tenant does not have any roles')
    roles = str_roles.lower().split(',')
    authorized_roles_lower = [r.lower() for r in authorized_roles]
    for role in roles:
        if role in authorized_roles_lower:
            return
    raise falcon.HTTPUnauthorized('Forbidden',
                                  'Tenant ID is missing a required role to '
                                  'access this service')


def get_tenant_id(req):
    """Returns the tenant ID in the HTTP request header.

    :param req: HTTP request object.
    """
    return req.get_header('X-TENANT-ID')


def get_x_tenant_or_tenant_id(req, delegate_authorized_roles):
    """Evaluates whether the tenant ID or cross tenant ID should be returned.

    :param req: HTTP request object.
    :param delegate_authorized_roles: List of authorized roles that have
    delegate privileges.
    :returns: Returns the cross tenant or tenant ID.
    """
    if is_in_role(req, delegate_authorized_roles):
        params = falcon.uri.parse_query_string(req.query_string)
        if 'tenant_id' in params:
            tenant_id = params['tenant_id']
            return tenant_id
    return get_tenant_id(req)


def get_query_param(req, param_name, required=False, default_val=None):
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        if param_name in params:
            if isinstance(params[param_name], list):
                param_val = params[param_name][0].decode('utf8')
            else:
                param_val = params[param_name].decode('utf8')

            return param_val
        else:
            if required:
                raise Exception("Missing " + param_name)
            else:
                return default_val
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def get_query_name(req, name_required=False):
    """Returns the query param "name" if supplied.

    :param req: HTTP request object.
    """
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        if 'name' in params:
            name = params['name']
            return name
        else:
            if name_required:
                raise Exception("Missing name")
            else:
                return ''
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def get_query_dimensions(req):
    """Gets and parses the query param dimensions.

    :param req: HTTP request object.
    :return: Returns the dimensions as a JSON object
    :raises falcon.HTTPBadRequest: If dimensions are malformed.
    """
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        dimensions = {}
        if 'dimensions' in params:
            dimensions_param = params['dimensions']

            if isinstance(dimensions_param, basestring):
                dimensions_str_array = [dimensions_param, ]
            else:
                dimensions_str_array = [
                    s for sublist in dimensions_param
                    for s in sublist.split(",")]

            for dimension in dimensions_str_array:
                dimension_name_value = dimension.split(':')
                if len(dimension_name_value) == 2:
                    dimensions[dimension_name_value[0]] = dimension_name_value[
                        1]
                else:
                    raise Exception('Dimensions are malformed')
        return dimensions
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def get_query_starttime_timestamp(req, required=True):
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        if 'start_time' in params:
            return _convert_time_string(params['start_time'])
        else:
            if required:
                raise Exception("Missing start time")
            else:
                return None
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def get_query_endtime_timestamp(req, required=True):
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        if 'end_time' in params:
            return _convert_time_string(params['end_time'])
        else:
            if required:
                raise Exception("Missing end time")
            else:
                return None
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def validate_start_end_timestamps(start_timestamp, end_timestamp=None):
    if end_timestamp:
        if not start_timestamp < end_timestamp:
            raise falcon.HTTPBadRequest('Bad request',
                                        'start_time must be before end_time')


def _convert_time_string(date_time_string):
    dt = timeutils.parse_isotime(date_time_string)
    dt = timeutils.normalize_time(dt)
    timestamp = (dt - datetime.datetime(1970, 1, 1)).total_seconds()
    return timestamp


def get_query_statistics(req):
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        if 'statistics' in params:
            statistics = []
            # falcon may return this as a list or as a string
            if isinstance(params['statistics'], list):
                statistics.extend(params['statistics'])
            else:
                statistics.append(params['statistics'])
            statistics = [statistic.lower() for statistic in statistics]
            if not all(statistic in ['avg', 'min', 'max', 'count', 'sum'] for
                       statistic in statistics):
                raise Exception("Invalid statistic")
            return statistics
        else:
            raise Exception("Missing statistics")
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def get_query_period(req):
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        if 'period' in params:
            period = params['period']
            try:
                period = int(period)
            except Exception:
                raise Exception("Period must be a valid integer")
            if period < 0:
                raise Exception("Period must be a positive integer")
            return str(period)
        else:
            return None
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def validate_query_name(name):
    """Validates the query param name.

    :param name: Query param name.
    :raises falcon.HTTPBadRequest: If name is not valid.
    """
    try:
        metric_name_schema.validate(name)
    except schemas_exceptions.ValidationException as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def validate_query_dimensions(dimensions):
    """Validates the query param dimensions.

    :param dimensions: Query param dimensions.
    :raises falcon.HTTPBadRequest: If dimensions are not valid.
    """
    try:
        dimensions_schema.validate(dimensions)
    except schemas_exceptions.ValidationException as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def paginate(resource, uri, limit):
    parsed_uri = urlparse.urlparse(uri)

    self_link = build_base_uri(parsed_uri)

    old_query_params = _get_old_query_params(parsed_uri)

    if old_query_params:
        self_link += '?' + '&'.join(old_query_params)

    if resource and len(resource) > limit:

        if 'timestamp' in resource[limit - 1]:
            new_offset = resource[limit - 1]['timestamp']

        if 'id' in resource[limit - 1]:
            new_offset = resource[limit - 1]['id']

        next_link = build_base_uri(parsed_uri)

        new_query_params = [u'offset' + '=' + urllib.quote(
            new_offset.encode('utf8'), safe='')]

        _get_old_query_params_except_offset(new_query_params, parsed_uri)

        if new_query_params:
            next_link += '?' + '&'.join(new_query_params)

        resource = {u'links': ([{u'rel': u'self',
                                 u'href': self_link.decode('utf8')},
                                {u'rel': u'next',
                                 u'href': next_link.decode('utf8')}]),
                    u'elements': resource[:limit]}

    else:

        resource = {u'links': ([{u'rel': u'self',
                                 u'href': self_link.decode('utf8')}]),
                    u'elements': resource}

    return resource


def paginate_measurement(measurement, uri, limit):
    parsed_uri = urlparse.urlparse(uri)

    self_link = build_base_uri(parsed_uri)

    old_query_params = _get_old_query_params(parsed_uri)

    if old_query_params:
        self_link += '?' + '&'.join(old_query_params)

    if (measurement
            and measurement[0]
            and measurement[0]['measurements']
            and len(measurement[0]['measurements']) > limit):

        new_offset = measurement[0]['measurements'][limit - 1][0]

        next_link = build_base_uri(parsed_uri)

        new_query_params = [u'offset' + '=' + urllib.quote(
            new_offset.encode('utf8'), safe='')]

        _get_old_query_params_except_offset(new_query_params, parsed_uri)

        if new_query_params:
            next_link += '?' + '&'.join(new_query_params)

        truncated_measurement = [{u'dimensions': measurement[0]['dimensions'],
                                  u'measurements': (measurement[0]
                                                    ['measurements'][:limit]),
                                  u'name': measurement[0]['name'],
                                  u'columns': measurement[0]['columns'],
                                  u'id': new_offset}]

        resource = {u'links': ([{u'rel': u'self',
                                 u'href': self_link.decode('utf8')},
                                {u'rel': u'next',
                                 u'href': next_link.decode('utf8')}]),
                    u'elements': truncated_measurement}

    else:

        resource = {u'links': ([{u'rel': u'self',
                                 u'href': self_link.decode('utf8')}]),
                    u'elements': measurement}

    return resource


def _get_old_query_params(parsed_uri):
    old_query_params = []

    if parsed_uri.query:

        for query_param in parsed_uri.query.split('&'):
            query_param_name, query_param_val = query_param.split('=')

            old_query_params.append(urllib.quote(
                query_param_name.encode('utf8'), safe='')
                + "="
                + urllib.quote(query_param_val.encode('utf8'), safe=''))

    return old_query_params


def _get_old_query_params_except_offset(new_query_params, parsed_uri):
    if parsed_uri.query:

        for query_param in parsed_uri.query.split('&'):
            query_param_name, query_param_val = query_param.split('=')
            if query_param_name.lower() != 'offset':
                new_query_params.append(urllib.quote(
                    query_param_name.encode(
                        'utf8'), safe='') + "=" + urllib.quote(
                    query_param_val.encode(
                        'utf8'), safe=''))


def paginate_statistics(statistic, uri, limit):
    parsed_uri = urlparse.urlparse(uri)

    self_link = build_base_uri(parsed_uri)

    old_query_params = _get_old_query_params(parsed_uri)

    if old_query_params:
        self_link += '?' + '&'.join(old_query_params)

    if (statistic
            and statistic[0]
            and statistic[0]['statistics']
            and len(statistic[0]['statistics']) > limit):

        new_offset = (
            statistic[0]['statistics'][limit - 1][0])

        next_link = build_base_uri(parsed_uri)

        new_query_params = [u'offset' + '=' + urllib.quote(
            new_offset.encode('utf8'), safe='')]

        _get_old_query_params_except_offset(new_query_params, parsed_uri)

        if new_query_params:
            next_link += '?' + '&'.join(new_query_params)

        truncated_statistic = [{u'dimensions': statistic[0]['dimensions'],
                                u'statistics': (statistic[0]['statistics'][:limit]),
                                u'name': statistic[0]['name'],
                                u'columns': statistic[0]['columns'],
                                u'id': new_offset}]

        resource = {u'links': ([{u'rel': u'self',
                                 u'href': self_link.decode('utf8')},
                                {u'rel': u'next',
                                 u'href': next_link.decode('utf8')}]),
                    u'elements': truncated_statistic}

    else:

        resource = {u'links': ([{u'rel': u'self',
                                 u'href': self_link.decode('utf8')}]),
                    u'elements': statistic}

    return resource


def build_base_uri(parsed_uri):
    return parsed_uri.scheme + '://' + parsed_uri.netloc + parsed_uri.path


def get_link(uri, resource_id, rel='self'):
    """Returns a link dictionary containing href, and rel.

    :param uri: the http request.uri.
    :param resource_id: the id of the resource
    """
    parsed_uri = urlparse.urlparse(uri)
    href = build_base_uri(parsed_uri)
    href += '/' + resource_id

    if rel:
        link_dict = dict(href=href, rel=rel)
    else:
        link_dict = dict(href=href)

    return link_dict


def add_links_to_resource(resource, uri, rel='self'):
    """Adds links to the given resource dictionary.

    :param resource: the resource dictionary you wish to add links.
    :param uri: the http request.uri.
    """
    resource['links'] = [get_link(uri, resource['id'], rel)]
    return resource


def add_links_to_resource_list(resourcelist, uri):
    """Adds links to the given resource dictionary list.

    :param resourcelist: the list of resources you wish to add links.
    :param uri: the http request.uri.
    """
    for resource in resourcelist:
        add_links_to_resource(resource, uri)
    return resourcelist


def read_http_resource(req):
    """Read from http request and return json.

    :param req: the http request.
    """
    try:
        msg = req.stream.read()
        json_msg = simplejson.loads(msg)
        return json_msg
    except ValueError as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', 'Request body is not valid JSON')


def raise_not_found_exception(resource_name, resource_id, tenant_id):
    """Provides exception for not found requests (update, delete, list).

    :param resource_name: the name of the resource.
    :param resource_id: id of the resource.
    :param tenant_id: id of the tenant
    """
    msg = 'No %s method exists for tenant_id = %s id = %s' % (
        resource_name, tenant_id, resource_id)
    raise falcon.HTTPError(
        status='404 Not Found',
        title='Not Found',
        description=msg,
        code=404)


def dumpit_utf8(thingy):
    return json.dumps(thingy, ensure_ascii=False).encode('utf8')


def str_2_bool(s):
    return s.lower() in ("true")


def get_limit(req):
    limit = get_query_param(req, 'limit')

    if limit:
        if limit.isdigit():
            limit = int(limit)
            if limit > constants.PAGE_LIMIT:
                return constants.PAGE_LIMIT
            else:
                return limit
        else:
            raise HTTPUnprocessableEntityError("Invalid limit",
                                               "Limit parameter must "
                                               "be a positive integer")
    else:
        return constants.PAGE_LIMIT

<filename>monasca_api/v2/reference/helpers.py
# Copyright 2014 Hewlett-Packard
# Copyright 2015 Cray Inc. All Rights Reserved.
#
# 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.

import datetime
import json
import urllib
import urlparse

import falcon
from oslo_log import log
from oslo_utils import timeutils
import simplejson

from monasca_api.common.repositories import constants
from monasca_api.v2.common.exceptions import HTTPUnprocessableEntityError
from monasca_api.v2.common.schemas import dimensions_schema
from monasca_api.v2.common.schemas import exceptions as schemas_exceptions
from monasca_api.v2.common.schemas import metric_name_schema

LOG = log.getLogger(__name__)


def read_json_msg_body(req):
    """Read the json_msg from the http request body and return them as JSON.

    :param req: HTTP request object.
    :return: Returns the metrics as a JSON object.
    :raises falcon.HTTPBadRequest:
    """
    try:
        msg = req.stream.read()
        json_msg = json.loads(msg)
        return json_msg
    except ValueError as ex:
        LOG.debug(ex)
        raise falcon.HTTPBadRequest('Bad request',
                                    'Request body is not valid JSON')


def validate_json_content_type(req):
    if req.content_type not in ['application/json']:
        raise falcon.HTTPBadRequest('Bad request', 'Bad content type. Must be '
                                                   'application/json')


def is_in_role(req, authorized_roles):
    """Is one or more of the X-ROLES in the supplied authorized_roles.

    :param req: HTTP request object. Must contain "X-ROLES" in the HTTP
    request header.
    :param authorized_roles: List of authorized roles to check against.
    :return: Returns True if in the list of authorized roles, otherwise False.
    """
    str_roles = req.get_header('X-ROLES')
    if str_roles is None:
        return False
    roles = str_roles.lower().split(',')
    for role in roles:
        if role in authorized_roles:
            return True
    return False


def validate_authorization(req, authorized_roles):
    """Validates whether one or more X-ROLES in the HTTP header is authorized.

    :param req: HTTP request object. Must contain "X-ROLES" in the HTTP
    request header.
    :param authorized_roles: List of authorized roles to check against.
    :raises falcon.HTTPUnauthorized
    """
    str_roles = req.get_header('X-ROLES')
    if str_roles is None:
        raise falcon.HTTPUnauthorized('Forbidden',
                                      'Tenant does not have any roles')
    roles = str_roles.lower().split(',')
    authorized_roles_lower = [r.lower() for r in authorized_roles]
    for role in roles:
        if role in authorized_roles_lower:
            return
    raise falcon.HTTPUnauthorized('Forbidden',
                                  'Tenant ID is missing a required role to '
                                  'access this service')


def get_tenant_id(req):
    """Returns the tenant ID in the HTTP request header.

    :param req: HTTP request object.
    """
    return req.get_header('X-TENANT-ID')


def get_x_tenant_or_tenant_id(req, delegate_authorized_roles):
    """Evaluates whether the tenant ID or cross tenant ID should be returned.

    :param req: HTTP request object.
    :param delegate_authorized_roles: List of authorized roles that have
    delegate privileges.
    :returns: Returns the cross tenant or tenant ID.
    """
    if is_in_role(req, delegate_authorized_roles):
        params = falcon.uri.parse_query_string(req.query_string)
        if 'tenant_id' in params:
            tenant_id = params['tenant_id']
            return tenant_id
    return get_tenant_id(req)


def get_query_param(req, param_name, required=False, default_val=None):
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        if param_name in params:
            if isinstance(params[param_name], list):
                param_val = params[param_name][0].decode('utf8')
            else:
                param_val = params[param_name].decode('utf8')

            return param_val
        else:
            if required:
                raise Exception("Missing " + param_name)
            else:
                return default_val
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def get_query_name(req, name_required=False):
    """Returns the query param "name" if supplied.

    :param req: HTTP request object.
    """
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        if 'name' in params:
            name = params['name']
            return name
        else:
            if name_required:
                raise Exception("Missing name")
            else:
                return ''
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def get_query_dimensions(req):
    """Gets and parses the query param dimensions.

    :param req: HTTP request object.
    :return: Returns the dimensions as a JSON object
    :raises falcon.HTTPBadRequest: If dimensions are malformed.
    """
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        dimensions = {}
        if 'dimensions' in params:
            dimensions_param = params['dimensions']

            if isinstance(dimensions_param, basestring):
                dimensions_str_array = [dimensions_param, ]
            else:
                dimensions_str_array = [
                    s for sublist in dimensions_param
                    for s in sublist.split(",")]

            for dimension in dimensions_str_array:
                dimension_name_value = dimension.split(':')
                if len(dimension_name_value) == 2:
                    dimensions[dimension_name_value[0]] = dimension_name_value[
                        1]
                else:
                    raise Exception('Dimensions are malformed')
        return dimensions
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def get_query_starttime_timestamp(req, required=True):
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        if 'start_time' in params:
            return _convert_time_string(params['start_time'])
        else:
            if required:
                raise Exception("Missing start time")
            else:
                return None
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def get_query_endtime_timestamp(req, required=True):
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        if 'end_time' in params:
            return _convert_time_string(params['end_time'])
        else:
            if required:
                raise Exception("Missing end time")
            else:
                return None
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def validate_start_end_timestamps(start_timestamp, end_timestamp=None):
    if end_timestamp:
        if not start_timestamp < end_timestamp:
            raise falcon.HTTPBadRequest('Bad request',
                                        'start_time must be before end_time')


def _convert_time_string(date_time_string):
    dt = timeutils.parse_isotime(date_time_string)
    dt = timeutils.normalize_time(dt)
    timestamp = (dt - datetime.datetime(1970, 1, 1)).total_seconds()
    return timestamp


def get_query_statistics(req):
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        if 'statistics' in params:
            statistics = []
            # falcon may return this as a list or as a string
            if isinstance(params['statistics'], list):
                statistics.extend(params['statistics'])
            else:
                statistics.append(params['statistics'])
            statistics = [statistic.lower() for statistic in statistics]
            if not all(statistic in ['avg', 'min', 'max', 'count', 'sum'] for
                       statistic in statistics):
                raise Exception("Invalid statistic")
            return statistics
        else:
            raise Exception("Missing statistics")
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def get_query_period(req):
    try:
        params = falcon.uri.parse_query_string(req.query_string)
        if 'period' in params:
            period = params['period']
            try:
                period = int(period)
            except Exception:
                raise Exception("Period must be a valid integer")
            if period < 0:
                raise Exception("Period must be a positive integer")
            return str(period)
        else:
            return None
    except Exception as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def validate_query_name(name):
    """Validates the query param name.

    :param name: Query param name.
    :raises falcon.HTTPBadRequest: If name is not valid.
    """
    try:
        metric_name_schema.validate(name)
    except schemas_exceptions.ValidationException as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def validate_query_dimensions(dimensions):
    """Validates the query param dimensions.

    :param dimensions: Query param dimensions.
    :raises falcon.HTTPBadRequest: If dimensions are not valid.
    """
    try:
        dimensions_schema.validate(dimensions)
    except schemas_exceptions.ValidationException as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)


def paginate(resource, uri, limit):
    parsed_uri = urlparse.urlparse(uri)

    self_link = build_base_uri(parsed_uri)

    old_query_params = _get_old_query_params(parsed_uri)

    if old_query_params:
        self_link += '?' + '&'.join(old_query_params)

    if resource and len(resource) > limit:

        if 'timestamp' in resource[limit - 1]:
            new_offset = resource[limit - 1]['timestamp']

        if 'id' in resource[limit - 1]:
            new_offset = resource[limit - 1]['id']

        next_link = build_base_uri(parsed_uri)

        new_query_params = [u'offset' + '=' + urllib.quote(
            new_offset.encode('utf8'), safe='')]

        _get_old_query_params_except_offset(new_query_params, parsed_uri)

        if new_query_params:
            next_link += '?' + '&'.join(new_query_params)

        resource = {u'links': ([{u'rel': u'self',
                                 u'href': self_link.decode('utf8')},
                                {u'rel': u'next',
                                 u'href': next_link.decode('utf8')}]),
                    u'elements': resource[:limit]}

    else:

        resource = {u'links': ([{u'rel': u'self',
                                 u'href': self_link.decode('utf8')}]),
                    u'elements': resource}

    return resource


def paginate_measurement(measurement, uri, limit):
    parsed_uri = urlparse.urlparse(uri)

    self_link = build_base_uri(parsed_uri)

    old_query_params = _get_old_query_params(parsed_uri)

    if old_query_params:
        self_link += '?' + '&'.join(old_query_params)

    if (measurement
            and measurement[0]
            and measurement[0]['measurements']
            and len(measurement[0]['measurements']) > limit):

        new_offset = measurement[0]['measurements'][limit - 1][0]

        next_link = build_base_uri(parsed_uri)

        new_query_params = [u'offset' + '=' + urllib.quote(
            new_offset.encode('utf8'), safe='')]

        _get_old_query_params_except_offset(new_query_params, parsed_uri)

        if new_query_params:
            next_link += '?' + '&'.join(new_query_params)

        truncated_measurement = [{u'dimensions': measurement[0]['dimensions'],
                                  u'measurements': (measurement[0]
                                                    ['measurements'][:limit]),
                                  u'name': measurement[0]['name'],
                                  u'columns': measurement[0]['columns'],
                                  u'id': new_offset}]

        resource = {u'links': ([{u'rel': u'self',
                                 u'href': self_link.decode('utf8')},
                                {u'rel': u'next',
                                 u'href': next_link.decode('utf8')}]),
                    u'elements': truncated_measurement}

    else:

        resource = {u'links': ([{u'rel': u'self',
                                 u'href': self_link.decode('utf8')}]),
                    u'elements': measurement}

    return resource


def _get_old_query_params(parsed_uri):
    old_query_params = []

    if parsed_uri.query:

        for query_param in parsed_uri.query.split('&'):
            query_param_name, query_param_val = query_param.split('=')

            old_query_params.append(urllib.quote(
                query_param_name.encode('utf8'), safe='')
                + "="
                + urllib.quote(query_param_val.encode('utf8'), safe=''))

    return old_query_params


def _get_old_query_params_except_offset(new_query_params, parsed_uri):
    if parsed_uri.query:

        for query_param in parsed_uri.query.split('&'):
            query_param_name, query_param_val = query_param.split('=')
            if query_param_name.lower() != 'offset':
                new_query_params.append(urllib.quote(
                    query_param_name.encode(
                        'utf8'), safe='') + "=" + urllib.quote(
                    query_param_val.encode(
                        'utf8'), safe=''))


def paginate_statistics(statistic, uri, limit):
    parsed_uri = urlparse.urlparse(uri)

    self_link = build_base_uri(parsed_uri)

    old_query_params = _get_old_query_params(parsed_uri)

    if old_query_params:
        self_link += '?' + '&'.join(old_query_params)

    if (statistic
            and statistic[0]
            and statistic[0]['statistics']
            and len(statistic[0]['statistics']) > limit):

        new_offset = (
            statistic[0]['statistics'][limit - 1][0])

        next_link = build_base_uri(parsed_uri)

        new_query_params = [u'offset' + '=' + urllib.quote(
            new_offset.encode('utf8'), safe='')]

        _get_old_query_params_except_offset(new_query_params, parsed_uri)

        if new_query_params:
            next_link += '?' + '&'.join(new_query_params)

        truncated_statistic = [{u'dimensions': statistic[0]['dimensions'],
                                u'statistics': (statistic[0]['statistics'][:limit]),
                                u'name': statistic[0]['name'],
                                u'columns': statistic[0]['columns'],
                                u'id': new_offset}]

        resource = {u'links': ([{u'rel': u'self',
                                 u'href': self_link.decode('utf8')},
                                {u'rel': u'next',
                                 u'href': next_link.decode('utf8')}]),
                    u'elements': truncated_statistic}

    else:

        resource = {u'links': ([{u'rel': u'self',
                                 u'href': self_link.decode('utf8')}]),
                    u'elements': statistic}

    return resource


def build_base_uri(parsed_uri):
    return parsed_uri.scheme + '://' + parsed_uri.netloc + parsed_uri.path


def get_link(uri, resource_id, rel='self'):
    """Returns a link dictionary containing href, and rel.

    :param uri: the http request.uri.
    :param resource_id: the id of the resource
    """
    parsed_uri = urlparse.urlparse(uri)
    href = build_base_uri(parsed_uri)
    href += '/' + resource_id

    if rel:
        link_dict = dict(href=href, rel=rel)
    else:
        link_dict = dict(href=href)

    return link_dict


def add_links_to_resource(resource, uri, rel='self'):
    """Adds links to the given resource dictionary.

    :param resource: the resource dictionary you wish to add links.
    :param uri: the http request.uri.
    """
    resource['links'] = [get_link(uri, resource['id'], rel)]
    return resource


def add_links_to_resource_list(resourcelist, uri):
    """Adds links to the given resource dictionary list.

    :param resourcelist: the list of resources you wish to add links.
    :param uri: the http request.uri.
    """
    for resource in resourcelist:
        add_links_to_resource(resource, uri)
    return resourcelist


def read_http_resource(req):
    """Read from http request and return json.

    :param req: the http request.
    """
    try:
        msg = req.stream.read()
        json_msg = simplejson.loads(msg)
        return json_msg
    except ValueError as ex:
        LOG.debug(ex)
        raise HTTPUnprocessableEntityError('Unprocessable Entity', 'Request body is not valid JSON')


def raise_not_found_exception(resource_name, resource_id, tenant_id):
    """Provides exception for not found requests (update, delete, list).

    :param resource_name: the name of the resource.
    :param resource_id: id of the resource.
    :param tenant_id: id of the tenant
    """
    msg = 'No %s method exists for tenant_id = %s id = %s' % (
        resource_name, tenant_id, resource_id)
    raise falcon.HTTPError(
        status='404 Not Found',
        title='Not Found',
        description=msg,
        code=404)


def dumpit_utf8(thingy):
    return json.dumps(thingy, ensure_ascii=False).encode('utf8')


def str_2_bool(s):
    return s.lower() in ("true")


def get_limit(req):
    limit = get_query_param(req, 'limit')

    if limit:
        if limit.isdigit():
            limit = int(limit)
            if limit > constants.PAGE_LIMIT:
                return constants.PAGE_LIMIT
            else:
                return limit
        else:
            raise HTTPUnprocessableEntityError("Invalid limit",
                                               "Limit parameter must "
                                               "be a positive integer")
    else:
        return constants.PAGE_LIMIT

# Copyright 2014 Hewlett-Packard # Copyright 2015 Cray Inc. All Rights Reserved. # # 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. Read the json_msg from the http request body and return them as JSON.      :param req: HTTP request object.     :return: Returns the metrics as a JSON object.     :raises falcon.HTTPBadRequest: Is one or more of the X-ROLES in the supplied authorized_roles.      :param req: HTTP request object. Must contain "X-ROLES" in the HTTP     request header.     :param authorized_roles: List of authorized roles to check against.     :return: Returns True if in the list of authorized roles, otherwise False. Validates whether one or more X-ROLES in the HTTP header is authorized.      :param req: HTTP request object. Must contain "X-ROLES" in the HTTP     request header.     :param authorized_roles: List of authorized roles to check against.     :raises falcon.HTTPUnauthorized Returns the tenant ID in the HTTP request header.      :param req: HTTP request object. Evaluates whether the tenant ID or cross tenant ID should be returned.      :param req: HTTP request object.     :param delegate_authorized_roles: List of authorized roles that have     delegate privileges.     :returns: Returns the cross tenant or tenant ID. Returns the query param "name" if supplied.      :param req: HTTP request object. Gets and parses the query param dimensions.      :param req: HTTP request object.     :return: Returns the dimensions as a JSON object     :raises falcon.HTTPBadRequest: If dimensions are malformed. # falcon may return this as a list or as a string Validates the query param name.      :param name: Query param name.     :raises falcon.HTTPBadRequest: If name is not valid. Validates the query param dimensions.      :param dimensions: Query param dimensions.     :raises falcon.HTTPBadRequest: If dimensions are not valid. Returns a link dictionary containing href, and rel.      :param uri: the http request.uri.     :param resource_id: the id of the resource Adds links to the given resource dictionary.      :param resource: the resource dictionary you wish to add links.     :param uri: the http request.uri. Adds links to the given resource dictionary list.      :param resourcelist: the list of resources you wish to add links.     :param uri: the http request.uri. Read from http request and return json.      :param req: the http request. Provides exception for not found requests (update, delete, list).      :param resource_name: the name of the resource.     :param resource_id: id of the resource.     :param tenant_id: id of the tenant