# # Copyright Ericsson AB 2013. All rights reserved # # Authors: Ildiko Vancsa # Balazs Gibizer # # 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. """Test the methods related to complex query.""" import datetime import fixtures import jsonschema import mock import wsme from ceilometer.alarm.storage import models as alarm_models from ceilometer.api.controllers import v2 as api from ceilometer.openstack.common import test from ceilometer.storage import models class FakeComplexQuery(api.ValidatedComplexQuery): def __init__(self, db_model, additional_name_mapping=None, metadata=False): super(FakeComplexQuery, self).__init__(query=None, db_model=db_model, additional_name_mapping=( additional_name_mapping or {}), metadata_allowed=metadata) sample_name_mapping = {"resource": "resource_id", "meter": "counter_name", "type": "counter_type", "unit": "counter_unit", "volume": "counter_volume"} class TestComplexQuery(test.BaseTestCase): def setUp(self): super(TestComplexQuery, self).setUp() self.useFixture(fixtures.MonkeyPatch( 'pecan.response', mock.MagicMock())) self.query = FakeComplexQuery(models.Sample, sample_name_mapping, True) self.query_alarm = FakeComplexQuery(alarm_models.Alarm) self.query_alarmchange = FakeComplexQuery( alarm_models.AlarmChange) def test_replace_isotime_utc(self): filter_expr = {"=": {"timestamp": "2013-12-05T19:38:29Z"}} self.query._replace_isotime_with_datetime(filter_expr) self.assertEqual(datetime.datetime(2013, 12, 5, 19, 38, 29), filter_expr["="]["timestamp"]) def test_replace_isotime_timezone_removed(self): filter_expr = {"=": {"timestamp": "2013-12-05T20:38:29+01:00"}} self.query._replace_isotime_with_datetime(filter_expr) self.assertEqual(datetime.datetime(2013, 12, 5, 20, 38, 29), filter_expr["="]["timestamp"]) def test_replace_isotime_wrong_syntax(self): filter_expr = {"=": {"timestamp": "not a valid isotime string"}} self.assertRaises(wsme.exc.ClientSideError, self.query._replace_isotime_with_datetime, filter_expr) def test_replace_isotime_in_complex_filter(self): filter_expr = {"and": [{"=": {"timestamp": "2013-12-05T19:38:29Z"}}, {"=": {"timestamp": "2013-12-06T19:38:29Z"}}]} self.query._replace_isotime_with_datetime(filter_expr) self.assertEqual(datetime.datetime(2013, 12, 5, 19, 38, 29), filter_expr["and"][0]["="]["timestamp"]) self.assertEqual(datetime.datetime(2013, 12, 6, 19, 38, 29), filter_expr["and"][1]["="]["timestamp"]) def test_replace_isotime_in_complex_filter_with_unbalanced_tree(self): subfilter = {"and": [{"=": {"project_id": 42}}, {"=": {"timestamp": "2013-12-06T19:38:29Z"}}]} filter_expr = {"or": [{"=": {"timestamp": "2013-12-05T19:38:29Z"}}, subfilter]} self.query._replace_isotime_with_datetime(filter_expr) self.assertEqual(datetime.datetime(2013, 12, 5, 19, 38, 29), filter_expr["or"][0]["="]["timestamp"]) self.assertEqual(datetime.datetime(2013, 12, 6, 19, 38, 29), filter_expr["or"][1]["and"][1]["="]["timestamp"]) def test_convert_operator_to_lower_case(self): filter_expr = {"AND": [{"=": {"project_id": 42}}, {"=": {"project_id": 44}}]} self.query._convert_operator_to_lower_case(filter_expr) self.assertEqual("and", filter_expr.keys()[0]) filter_expr = {"Or": [{"=": {"project_id": 43}}, {"anD": [{"=": {"project_id": 44}}, {"=": {"project_id": 42}}]}]} self.query._convert_operator_to_lower_case(filter_expr) self.assertEqual("or", filter_expr.keys()[0]) self.assertEqual("and", filter_expr["or"][1].keys()[0]) def test_invalid_filter_misstyped_field_name_samples(self): filter = {"=": {"project_id11": 42}} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_invalid_filter_misstyped_field_name_alarms(self): filter = {"=": {"enabbled": True}} self.assertRaises(jsonschema.ValidationError, self.query_alarm._validate_filter, filter) def test_invalid_filter_misstyped_field_name_alarmchange(self): filter = {"=": {"tpe": "rule change"}} self.assertRaises(jsonschema.ValidationError, self.query_alarmchange._validate_filter, filter) def test_invalid_complex_filter_wrong_field_names(self): filter = {"and": [{"=": {"non_existing_field": 42}}, {"=": {"project_id": 42}}]} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) filter = {"and": [{"=": {"project_id": 42}}, {"=": {"non_existing_field": 42}}]} self.assertRaises(jsonschema.ValidationError, self.query_alarm._validate_filter, filter) filter = {"and": [{"=": {"project_id11": 42}}, {"=": {"project_id": 42}}]} self.assertRaises(jsonschema.ValidationError, self.query_alarmchange._validate_filter, filter) filter = {"or": [{"=": {"non_existing_field": 42}}, {"and": [{"=": {"project_id": 44}}, {"=": {"project_id": 42}}]}]} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) filter = {"or": [{"=": {"project_id": 43}}, {"and": [{"=": {"project_id": 44}}, {"=": {"non_existing_field": 42}}]}]} self.assertRaises(jsonschema.ValidationError, self.query_alarm._validate_filter, filter) def test_convert_orderby(self): orderby = [] self.query._convert_orderby_to_lower_case(orderby) self.assertEqual([], orderby) orderby = [{"project_id": "DESC"}] self.query._convert_orderby_to_lower_case(orderby) self.assertEqual([{"project_id": "desc"}], orderby) orderby = [{"project_id": "ASC"}, {"resource_id": "DESC"}] self.query._convert_orderby_to_lower_case(orderby) self.assertEqual([{"project_id": "asc"}, {"resource_id": "desc"}], orderby) def test_validate_orderby_empty_direction(self): orderby = [{"project_id": ""}] self.assertRaises(jsonschema.ValidationError, self.query._validate_orderby, orderby) orderby = [{"project_id": "asc"}, {"resource_id": ""}] self.assertRaises(jsonschema.ValidationError, self.query._validate_orderby, orderby) def test_validate_orderby_wrong_order_string(self): orderby = [{"project_id": "not a valid order"}] self.assertRaises(jsonschema.ValidationError, self.query._validate_orderby, orderby) def test_validate_orderby_wrong_multiple_item_order_string(self): orderby = [{"project_id": "not a valid order"}, {"resource_id": "ASC"}] self.assertRaises(jsonschema.ValidationError, self.query._validate_orderby, orderby) def test_validate_orderby_empty_field_name(self): orderby = [{"": "ASC"}] self.assertRaises(jsonschema.ValidationError, self.query._validate_orderby, orderby) orderby = [{"project_id": "asc"}, {"": "desc"}] self.assertRaises(jsonschema.ValidationError, self.query._validate_orderby, orderby) def test_validate_orderby_wrong_field_name(self): orderby = [{"project_id11": "ASC"}] self.assertRaises(jsonschema.ValidationError, self.query._validate_orderby, orderby) def test_validate_orderby_wrong_field_name_multiple_item_orderby(self): orderby = [{"project_id": "asc"}, {"resource_id11": "ASC"}] self.assertRaises(jsonschema.ValidationError, self.query._validate_orderby, orderby) def test_validate_orderby_metadata_is_not_allowed(self): orderby = [{"metadata.display_name": "asc"}] self.assertRaises(jsonschema.ValidationError, self.query._validate_orderby, orderby) class TestFilterSyntaxValidation(test.BaseTestCase): def setUp(self): super(TestFilterSyntaxValidation, self).setUp() self.query = FakeComplexQuery(models.Sample, sample_name_mapping, True) def test_simple_operator(self): filter = {"=": {"project_id": "string_value"}} self.query._validate_filter(filter) filter = {"=>": {"project_id": "string_value"}} self.query._validate_filter(filter) def test_valid_value_types(self): filter = {"=": {"project_id": "string_value"}} self.query._validate_filter(filter) filter = {"=": {"project_id": 42}} self.query._validate_filter(filter) filter = {"=": {"project_id": 3.14}} self.query._validate_filter(filter) filter = {"=": {"project_id": True}} self.query._validate_filter(filter) filter = {"=": {"project_id": False}} self.query._validate_filter(filter) def test_invalid_simple_operator(self): filter = {"==": {"project_id": "string_value"}} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) filter = {"": {"project_id": "string_value"}} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_more_than_one_operator_is_invalid(self): filter = {"=": {"project_id": "string_value"}, "<": {"": ""}} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_empty_expression_is_invalid(self): filter = {} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_invalid_field_name(self): filter = {"=": {"": "value"}} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) filter = {"=": {" ": "value"}} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) filter = {"=": {"\t": "value"}} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_more_than_one_field_is_invalid(self): filter = {"=": {"project_id": "value", "resource_id": "value"}} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_missing_field_after_simple_op_is_invalid(self): filter = {"=": {}} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_and_or(self): filter = {"and": [{"=": {"project_id": "string_value"}}, {"=": {"resource_id": "value"}}]} self.query._validate_filter(filter) filter = {"or": [{"and": [{"=": {"project_id": "string_value"}}, {"=": {"resource_id": "value"}}]}, {"=": {"counter_name": "value"}}]} self.query._validate_filter(filter) filter = {"or": [{"and": [{"=": {"project_id": "string_value"}}, {"=": {"resource_id": "value"}}, {"<": {"counter_name": 42}}]}, {"=": {"counter_name": "value"}}]} self.query._validate_filter(filter) def test_complex_operator_with_in(self): filter = {"and": [{"<": {"counter_volume": 42}}, {">=": {"counter_volume": 36}}, {"in": {"project_id": ["project_id1", "project_id2", "project_id3"]}}]} self.query._validate_filter(filter) def test_invalid_complex_operator(self): filter = {"xor": [{"=": {"project_id": "string_value"}}, {"=": {"resource_id": "value"}}]} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_and_or_with_one_child_is_invalid(self): filter = {"or": [{"=": {"project_id": "string_value"}}]} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_complex_operator_with_zero_child_is_invalid(self): filter = {"or": []} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_more_than_one_complex_operator_is_invalid(self): filter = {"and": [{"=": {"project_id": "string_value"}}, {"=": {"resource_id": "value"}}], "or": [{"=": {"project_id": "string_value"}}, {"=": {"resource_id": "value"}}]} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_not(self): filter = {"not": {"=": {"project_id": "value"}}} self.query._validate_filter(filter) filter = { "not": {"or": [{"and": [{"=": {"project_id": "string_value"}}, {"=": {"resource_id": "value"}}, {"<": {"counter_name": 42}}]}, {"=": {"counter_name": "value"}}]}} self.query._validate_filter(filter) def test_not_with_zero_child_is_invalid(self): filter = {"not": {}} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_not_with_more_than_one_child_is_invalid(self): filter = {"not": {"=": {"project_id": "value"}, "!=": {"resource_id": "value"}}} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter) def test_empty_in_query_not_passing(self): filter = {"in": {"resource_id": []}} self.assertRaises(jsonschema.ValidationError, self.query._validate_filter, filter)