1918 lines
80 KiB
Python
1918 lines
80 KiB
Python
# -*- encoding: utf-8 -*-
|
|
#
|
|
# Copyright © 2016 Red Hat, Inc.
|
|
# Copyright © 2014-2015 eNovance
|
|
#
|
|
# 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 base64
|
|
import calendar
|
|
import contextlib
|
|
import datetime
|
|
from email import utils as email_utils
|
|
import hashlib
|
|
import json
|
|
import uuid
|
|
|
|
from keystonemiddleware import fixture as ksm_fixture
|
|
import mock
|
|
from oslo_utils import timeutils
|
|
import six
|
|
from stevedore import extension
|
|
import testscenarios
|
|
from testtools import testcase
|
|
import webtest
|
|
|
|
from gnocchi import archive_policy
|
|
from gnocchi import rest
|
|
from gnocchi.rest import app
|
|
from gnocchi.tests import base as tests_base
|
|
from gnocchi import utils
|
|
|
|
|
|
load_tests = testscenarios.load_tests_apply_scenarios
|
|
|
|
|
|
class TestingApp(webtest.TestApp):
|
|
VALID_TOKEN_ADMIN = str(uuid.uuid4())
|
|
USER_ID_ADMIN = str(uuid.uuid4())
|
|
PROJECT_ID_ADMIN = str(uuid.uuid4())
|
|
|
|
VALID_TOKEN = str(uuid.uuid4())
|
|
USER_ID = str(uuid.uuid4())
|
|
PROJECT_ID = str(uuid.uuid4())
|
|
|
|
VALID_TOKEN_2 = str(uuid.uuid4())
|
|
USER_ID_2 = str(uuid.uuid4())
|
|
PROJECT_ID_2 = str(uuid.uuid4())
|
|
|
|
INVALID_TOKEN = str(uuid.uuid4())
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.auth_mode = kwargs.pop('auth_mode')
|
|
self.storage = kwargs.pop('storage')
|
|
self.indexer = kwargs.pop('indexer')
|
|
super(TestingApp, self).__init__(*args, **kwargs)
|
|
# Setup Keystone auth_token fake cache
|
|
self.token = self.VALID_TOKEN
|
|
# Setup default user for basic auth
|
|
self.user = self.USER_ID.encode('ascii')
|
|
|
|
@contextlib.contextmanager
|
|
def use_admin_user(self):
|
|
if self.auth_mode == "keystone":
|
|
old_token = self.token
|
|
self.token = self.VALID_TOKEN_ADMIN
|
|
try:
|
|
yield
|
|
finally:
|
|
self.token = old_token
|
|
elif self.auth_mode == "basic":
|
|
old_user = self.user
|
|
self.user = b"admin"
|
|
try:
|
|
yield
|
|
finally:
|
|
self.user = old_user
|
|
elif self.auth_mode == "noauth":
|
|
raise testcase.TestSkipped("auth mode is noauth")
|
|
else:
|
|
raise RuntimeError("Unknown auth_mode")
|
|
|
|
@contextlib.contextmanager
|
|
def use_another_user(self):
|
|
if self.auth_mode != "keystone":
|
|
raise testcase.TestSkipped("Auth mode is not Keystone")
|
|
old_token = self.token
|
|
self.token = self.VALID_TOKEN_2
|
|
try:
|
|
yield
|
|
finally:
|
|
self.token = old_token
|
|
|
|
@contextlib.contextmanager
|
|
def use_invalid_token(self):
|
|
if self.auth_mode != "keystone":
|
|
raise testcase.TestSkipped("Auth mode is not Keystone")
|
|
old_token = self.token
|
|
self.token = self.INVALID_TOKEN
|
|
try:
|
|
yield
|
|
finally:
|
|
self.token = old_token
|
|
|
|
def do_request(self, req, *args, **kwargs):
|
|
if self.auth_mode in "keystone":
|
|
if self.token is not None:
|
|
req.headers['X-Auth-Token'] = self.token
|
|
elif self.auth_mode == "basic":
|
|
req.headers['Authorization'] = (
|
|
b"basic " + base64.b64encode(self.user + b":")
|
|
)
|
|
elif self.auth_mode == "noauth":
|
|
req.headers['X-User-Id'] = self.USER_ID
|
|
req.headers['X-Project-Id'] = self.PROJECT_ID
|
|
response = super(TestingApp, self).do_request(req, *args, **kwargs)
|
|
metrics = self.storage.incoming.list_metric_with_measures_to_process(
|
|
None, None, full=True)
|
|
self.storage.process_background_tasks(self.indexer, metrics, sync=True)
|
|
return response
|
|
|
|
|
|
class RestTest(tests_base.TestCase, testscenarios.TestWithScenarios):
|
|
|
|
scenarios = [
|
|
('basic', dict(auth_mode="basic")),
|
|
('keystone', dict(auth_mode="keystone")),
|
|
('noauth', dict(auth_mode="noauth")),
|
|
]
|
|
|
|
def setUp(self):
|
|
super(RestTest, self).setUp()
|
|
|
|
if self.auth_mode == "keystone":
|
|
self.auth_token_fixture = self.useFixture(
|
|
ksm_fixture.AuthTokenFixture())
|
|
self.auth_token_fixture.add_token_data(
|
|
is_v2=True,
|
|
token_id=TestingApp.VALID_TOKEN_ADMIN,
|
|
user_id=TestingApp.USER_ID_ADMIN,
|
|
user_name='adminusername',
|
|
project_id=TestingApp.PROJECT_ID_ADMIN,
|
|
role_list=['admin'])
|
|
self.auth_token_fixture.add_token_data(
|
|
is_v2=True,
|
|
token_id=TestingApp.VALID_TOKEN,
|
|
user_id=TestingApp.USER_ID,
|
|
user_name='myusername',
|
|
project_id=TestingApp.PROJECT_ID,
|
|
role_list=["member"])
|
|
self.auth_token_fixture.add_token_data(
|
|
is_v2=True,
|
|
token_id=TestingApp.VALID_TOKEN_2,
|
|
user_id=TestingApp.USER_ID_2,
|
|
user_name='myusername2',
|
|
project_id=TestingApp.PROJECT_ID_2,
|
|
role_list=["member"])
|
|
|
|
self.conf.set_override("auth_mode", self.auth_mode, group="api")
|
|
|
|
self.app = TestingApp(app.load_app(conf=self.conf,
|
|
indexer=self.index,
|
|
storage=self.storage,
|
|
not_implemented_middleware=False),
|
|
storage=self.storage,
|
|
indexer=self.index,
|
|
auth_mode=self.auth_mode)
|
|
|
|
# NOTE(jd) Used at least by docs
|
|
@staticmethod
|
|
def runTest():
|
|
pass
|
|
|
|
|
|
class RootTest(RestTest):
|
|
def test_deserialize_force_json(self):
|
|
with self.app.use_admin_user():
|
|
self.app.post(
|
|
"/v1/archive_policy",
|
|
params="foo",
|
|
status=415)
|
|
|
|
def test_capabilities(self):
|
|
custom_agg = extension.Extension('test_aggregation', None, None, None)
|
|
mgr = extension.ExtensionManager.make_test_instance(
|
|
[custom_agg], 'gnocchi.aggregates')
|
|
aggregation_methods = set(
|
|
archive_policy.ArchivePolicy.VALID_AGGREGATION_METHODS)
|
|
|
|
with mock.patch.object(extension, 'ExtensionManager',
|
|
return_value=mgr):
|
|
result = self.app.get("/v1/capabilities").json
|
|
self.assertEqual(
|
|
sorted(aggregation_methods),
|
|
sorted(result['aggregation_methods']))
|
|
self.assertEqual(
|
|
['test_aggregation'],
|
|
result['dynamic_aggregation_methods'])
|
|
|
|
def test_status(self):
|
|
with self.app.use_admin_user():
|
|
r = self.app.get("/v1/status")
|
|
status = json.loads(r.text)
|
|
self.assertIsInstance(status['storage']['measures_to_process'], dict)
|
|
self.assertIsInstance(status['storage']['summary']['metrics'], int)
|
|
self.assertIsInstance(status['storage']['summary']['measures'], int)
|
|
|
|
|
|
class ArchivePolicyTest(RestTest):
|
|
"""Test the ArchivePolicies REST API.
|
|
|
|
See also gnocchi/tests/gabbi/gabbits/archive.yaml
|
|
"""
|
|
|
|
# TODO(chdent): The tests left here involve inspecting the
|
|
# aggregation methods which gabbi can't currently handle because
|
|
# the ordering of the results is not predictable.
|
|
|
|
def test_post_archive_policy_with_agg_methods(self):
|
|
name = str(uuid.uuid4())
|
|
with self.app.use_admin_user():
|
|
result = self.app.post_json(
|
|
"/v1/archive_policy",
|
|
params={"name": name,
|
|
"aggregation_methods": ["mean"],
|
|
"definition":
|
|
[{
|
|
"granularity": "1 minute",
|
|
"points": 20,
|
|
}]},
|
|
status=201)
|
|
self.assertEqual("application/json", result.content_type)
|
|
ap = json.loads(result.text)
|
|
self.assertEqual(['mean'], ap['aggregation_methods'])
|
|
|
|
def test_post_archive_policy_with_agg_methods_minus(self):
|
|
name = str(uuid.uuid4())
|
|
with self.app.use_admin_user():
|
|
result = self.app.post_json(
|
|
"/v1/archive_policy",
|
|
params={"name": name,
|
|
"aggregation_methods": ["-mean"],
|
|
"definition":
|
|
[{
|
|
"granularity": "1 minute",
|
|
"points": 20,
|
|
}]},
|
|
status=201)
|
|
self.assertEqual("application/json", result.content_type)
|
|
ap = json.loads(result.text)
|
|
self.assertEqual(
|
|
(set(self.conf.archive_policy.default_aggregation_methods)
|
|
- set(['mean'])),
|
|
set(ap['aggregation_methods']))
|
|
|
|
def test_get_archive_policy(self):
|
|
result = self.app.get("/v1/archive_policy/medium")
|
|
ap = json.loads(result.text)
|
|
ap_dict = self.archive_policies['medium'].jsonify()
|
|
ap_dict['definition'] = [
|
|
archive_policy.ArchivePolicyItem(**d).jsonify()
|
|
for d in ap_dict['definition']
|
|
]
|
|
self.assertEqual(set(ap['aggregation_methods']),
|
|
ap_dict['aggregation_methods'])
|
|
del ap['aggregation_methods']
|
|
del ap_dict['aggregation_methods']
|
|
self.assertEqual(ap_dict, ap)
|
|
|
|
def test_list_archive_policy(self):
|
|
result = self.app.get("/v1/archive_policy")
|
|
aps = json.loads(result.text)
|
|
# Transform list to set
|
|
for ap in aps:
|
|
ap['aggregation_methods'] = set(ap['aggregation_methods'])
|
|
for name, ap in six.iteritems(self.archive_policies):
|
|
apj = ap.jsonify()
|
|
apj['definition'] = [
|
|
archive_policy.ArchivePolicyItem(**d).jsonify()
|
|
for d in ap.definition
|
|
]
|
|
self.assertIn(apj, aps)
|
|
|
|
|
|
class MetricTest(RestTest):
|
|
|
|
def test_get_metric_with_another_user_linked_resource(self):
|
|
result = self.app.post_json(
|
|
"/v1/resource/generic",
|
|
params={
|
|
"id": str(uuid.uuid4()),
|
|
"started_at": "2014-01-01 02:02:02",
|
|
"user_id": TestingApp.USER_ID_2,
|
|
"project_id": TestingApp.PROJECT_ID_2,
|
|
"metrics": {"foobar": {"archive_policy_name": "low"}},
|
|
})
|
|
resource = json.loads(result.text)
|
|
metric_id = resource["metrics"]["foobar"]
|
|
with self.app.use_another_user():
|
|
self.app.get("/v1/metric/%s" % metric_id)
|
|
|
|
def test_get_metric_with_another_user(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"},
|
|
status=201)
|
|
self.assertEqual("application/json", result.content_type)
|
|
|
|
with self.app.use_another_user():
|
|
self.app.get(result.headers['Location'], status=403)
|
|
|
|
def test_post_archive_policy_no_mean(self):
|
|
"""Test that we have a 404 if mean is not in AP."""
|
|
ap = str(uuid.uuid4())
|
|
with self.app.use_admin_user():
|
|
self.app.post_json(
|
|
"/v1/archive_policy",
|
|
params={"name": ap,
|
|
"aggregation_methods": ["max"],
|
|
"definition": [{
|
|
"granularity": "10s",
|
|
"points": 20,
|
|
}]},
|
|
status=201)
|
|
result = self.app.post_json(
|
|
"/v1/metric",
|
|
params={"archive_policy_name": ap},
|
|
status=201)
|
|
metric = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:01',
|
|
"value": 8},
|
|
{"timestamp": '2013-01-01 12:00:02',
|
|
"value": 16}])
|
|
self.app.get("/v1/metric/%s/measures" % metric['id'],
|
|
status=404)
|
|
|
|
def test_delete_metric_another_user(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric = json.loads(result.text)
|
|
with self.app.use_another_user():
|
|
self.app.delete("/v1/metric/" + metric['id'], status=403)
|
|
|
|
def test_add_measure_with_another_user(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "high"})
|
|
metric = json.loads(result.text)
|
|
with self.app.use_another_user():
|
|
self.app.post_json(
|
|
"/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2013-01-01 23:23:23',
|
|
"value": 1234.2}],
|
|
status=403)
|
|
|
|
def test_add_measures_back_window(self):
|
|
ap_name = str(uuid.uuid4())
|
|
with self.app.use_admin_user():
|
|
self.app.post_json(
|
|
"/v1/archive_policy",
|
|
params={"name": ap_name,
|
|
"back_window": 2,
|
|
"definition":
|
|
[{
|
|
"granularity": "1 minute",
|
|
"points": 20,
|
|
}]},
|
|
status=201)
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": ap_name})
|
|
metric = json.loads(result.text)
|
|
self.app.post_json(
|
|
"/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2013-01-01 23:30:23',
|
|
"value": 1234.2}],
|
|
status=202)
|
|
self.app.post_json(
|
|
"/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2013-01-01 23:29:23',
|
|
"value": 1234.2}],
|
|
status=202)
|
|
self.app.post_json(
|
|
"/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2013-01-01 23:28:23',
|
|
"value": 1234.2}],
|
|
status=202)
|
|
# This one is too old and should not be taken into account
|
|
self.app.post_json(
|
|
"/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2012-01-01 23:27:23',
|
|
"value": 1234.2}],
|
|
status=202)
|
|
|
|
ret = self.app.get("/v1/metric/%s/measures" % metric['id'])
|
|
result = json.loads(ret.text)
|
|
self.assertEqual(
|
|
[[u'2013-01-01T23:28:00+00:00', 60.0, 1234.2],
|
|
[u'2013-01-01T23:29:00+00:00', 60.0, 1234.2],
|
|
[u'2013-01-01T23:30:00+00:00', 60.0, 1234.2]],
|
|
result)
|
|
|
|
def test_get_measure_with_another_user(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "low"})
|
|
metric = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2013-01-01 23:23:23',
|
|
"value": 1234.2}])
|
|
with self.app.use_another_user():
|
|
self.app.get("/v1/metric/%s/measures" % metric['id'],
|
|
status=403)
|
|
|
|
@mock.patch.object(timeutils, 'utcnow')
|
|
def test_get_measure_start_relative(self, utcnow):
|
|
"""Make sure the timestamps can be relative to now."""
|
|
utcnow.return_value = datetime.datetime(2014, 1, 1, 10, 23)
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "high"})
|
|
metric = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": utils.utcnow().isoformat(),
|
|
"value": 1234.2}])
|
|
ret = self.app.get(
|
|
"/v1/metric/%s/measures?start=-10 minutes"
|
|
% metric['id'],
|
|
status=200)
|
|
result = json.loads(ret.text)
|
|
now = utils.datetime_utc(2014, 1, 1, 10, 23)
|
|
self.assertEqual([
|
|
['2014-01-01T10:00:00+00:00', 3600.0, 1234.2],
|
|
[(now
|
|
- datetime.timedelta(
|
|
seconds=now.second,
|
|
microseconds=now.microsecond)).isoformat(),
|
|
60.0, 1234.2],
|
|
[(now
|
|
- datetime.timedelta(
|
|
microseconds=now.microsecond)).isoformat(),
|
|
1.0, 1234.2]], result)
|
|
|
|
def test_get_measure_stop(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "high"})
|
|
metric = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:00',
|
|
"value": 1234.2},
|
|
{"timestamp": '2013-01-01 12:00:02',
|
|
"value": 456}])
|
|
ret = self.app.get("/v1/metric/%s/measures"
|
|
"?stop=2013-01-01 12:00:01" % metric['id'],
|
|
status=200)
|
|
result = json.loads(ret.text)
|
|
self.assertEqual(
|
|
[[u'2013-01-01T12:00:00+00:00', 3600.0, 845.1],
|
|
[u'2013-01-01T12:00:00+00:00', 60.0, 845.1],
|
|
[u'2013-01-01T12:00:00+00:00', 1.0, 1234.2]],
|
|
result)
|
|
|
|
def test_get_measure_aggregation(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:01',
|
|
"value": 123.2},
|
|
{"timestamp": '2013-01-01 12:00:03',
|
|
"value": 12345.2},
|
|
{"timestamp": '2013-01-01 12:00:02',
|
|
"value": 1234.2}])
|
|
ret = self.app.get(
|
|
"/v1/metric/%s/measures?aggregation=max" % metric['id'],
|
|
status=200)
|
|
result = json.loads(ret.text)
|
|
self.assertEqual([[u'2013-01-01T00:00:00+00:00', 86400.0, 12345.2],
|
|
[u'2013-01-01T12:00:00+00:00', 3600.0, 12345.2],
|
|
[u'2013-01-01T12:00:00+00:00', 60.0, 12345.2]],
|
|
result)
|
|
|
|
def test_get_moving_average(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:00',
|
|
"value": 69},
|
|
{"timestamp": '2013-01-01 12:00:20',
|
|
"value": 42},
|
|
{"timestamp": '2013-01-01 12:00:40',
|
|
"value": 6},
|
|
{"timestamp": '2013-01-01 12:01:00',
|
|
"value": 44},
|
|
{"timestamp": '2013-01-01 12:01:20',
|
|
"value": 7}])
|
|
|
|
path = "/v1/metric/%s/measures?aggregation=%s&window=%ds"
|
|
ret = self.app.get(path % (metric['id'], 'moving-average', 120),
|
|
status=200)
|
|
result = json.loads(ret.text)
|
|
expected = [[u'2013-01-01T12:00:00+00:00', 120.0, 32.25]]
|
|
self.assertEqual(expected, result)
|
|
ret = self.app.get(path % (metric['id'], 'moving-average', 90),
|
|
status=400)
|
|
self.assertIn('No data available that is either full-res',
|
|
ret.text)
|
|
path = "/v1/metric/%s/measures?aggregation=%s"
|
|
ret = self.app.get(path % (metric['id'], 'moving-average'),
|
|
status=400)
|
|
self.assertIn('Moving aggregate must have window specified',
|
|
ret.text)
|
|
|
|
def test_get_moving_average_invalid_window(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:00',
|
|
"value": 69},
|
|
{"timestamp": '2013-01-01 12:00:20',
|
|
"value": 42},
|
|
{"timestamp": '2013-01-01 12:00:40',
|
|
"value": 6},
|
|
{"timestamp": '2013-01-01 12:01:00',
|
|
"value": 44},
|
|
{"timestamp": '2013-01-01 12:01:20',
|
|
"value": 7}])
|
|
|
|
path = "/v1/metric/%s/measures?aggregation=%s&window=foobar"
|
|
ret = self.app.get(path % (metric['id'], 'moving-average'),
|
|
status=400)
|
|
self.assertIn('Invalid value for window', ret.text)
|
|
|
|
def test_get_resource_missing_named_metric_measure_aggregation(self):
|
|
mgr = self.index.get_resource_type_schema()
|
|
resource_type = str(uuid.uuid4())
|
|
self.index.create_resource_type(
|
|
mgr.resource_type_from_dict(resource_type, {
|
|
"server_group": {"type": "string",
|
|
"min_length": 1,
|
|
"max_length": 40,
|
|
"required": True}
|
|
}, 'creating'))
|
|
|
|
attributes = {
|
|
"server_group": str(uuid.uuid4()),
|
|
}
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric1 = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric1['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:01',
|
|
"value": 8},
|
|
{"timestamp": '2013-01-01 12:00:02',
|
|
"value": 16}])
|
|
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric2 = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric2['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:01',
|
|
"value": 0},
|
|
{"timestamp": '2013-01-01 12:00:02',
|
|
"value": 4}])
|
|
|
|
attributes['id'] = str(uuid.uuid4())
|
|
attributes['metrics'] = {'foo': metric1['id']}
|
|
self.app.post_json("/v1/resource/" + resource_type,
|
|
params=attributes)
|
|
|
|
attributes['id'] = str(uuid.uuid4())
|
|
attributes['metrics'] = {'bar': metric2['id']}
|
|
self.app.post_json("/v1/resource/" + resource_type,
|
|
params=attributes)
|
|
|
|
result = self.app.post_json(
|
|
"/v1/aggregation/resource/%s/metric/foo?aggregation=max"
|
|
% resource_type,
|
|
params={"=": {"server_group": attributes['server_group']}})
|
|
|
|
measures = json.loads(result.text)
|
|
self.assertEqual([[u'2013-01-01T00:00:00+00:00', 86400.0, 16.0],
|
|
[u'2013-01-01T12:00:00+00:00', 3600.0, 16.0],
|
|
[u'2013-01-01T12:00:00+00:00', 60.0, 16.0]],
|
|
measures)
|
|
|
|
def test_search_value(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "high"})
|
|
metric = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:00',
|
|
"value": 1234.2},
|
|
{"timestamp": '2013-01-01 12:00:02',
|
|
"value": 456}])
|
|
metric1 = metric['id']
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "high"})
|
|
metric = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric['id'],
|
|
params=[{"timestamp": '2013-01-01 12:30:00',
|
|
"value": 1234.2},
|
|
{"timestamp": '2013-01-01 12:00:02',
|
|
"value": 456}])
|
|
metric2 = metric['id']
|
|
|
|
ret = self.app.post_json(
|
|
"/v1/search/metric?metric_id=%s&metric_id=%s"
|
|
"&stop=2013-01-01 12:10:00" % (metric1, metric2),
|
|
params={u"∧": [{u"≥": 1000}]},
|
|
status=200)
|
|
result = json.loads(ret.text)
|
|
self.assertEqual(
|
|
{metric1: [[u'2013-01-01T12:00:00+00:00', 1.0, 1234.2]],
|
|
metric2: []},
|
|
result)
|
|
|
|
|
|
class ResourceTest(RestTest):
|
|
def setUp(self):
|
|
super(ResourceTest, self).setUp()
|
|
self.attributes = {
|
|
"id": str(uuid.uuid4()),
|
|
"started_at": "2014-01-03T02:02:02+00:00",
|
|
"user_id": str(uuid.uuid4()),
|
|
"project_id": str(uuid.uuid4()),
|
|
"name": "my-name",
|
|
}
|
|
self.patchable_attributes = {
|
|
"ended_at": "2014-01-03T02:02:02+00:00",
|
|
"name": "new-name",
|
|
}
|
|
self.resource = self.attributes.copy()
|
|
# Set original_resource_id
|
|
self.resource['original_resource_id'] = self.resource['id']
|
|
self.resource['created_by_user_id'] = TestingApp.USER_ID
|
|
if self.auth_mode in ("keystone", "noauth"):
|
|
self.resource['created_by_project_id'] = TestingApp.PROJECT_ID
|
|
self.resource['creator'] = (
|
|
TestingApp.USER_ID + ":" + TestingApp.PROJECT_ID
|
|
)
|
|
elif self.auth_mode == "basic":
|
|
self.resource['created_by_project_id'] = ""
|
|
self.resource['creator'] = TestingApp.USER_ID
|
|
self.resource['ended_at'] = None
|
|
self.resource['metrics'] = {}
|
|
if 'user_id' not in self.resource:
|
|
self.resource['user_id'] = None
|
|
if 'project_id' not in self.resource:
|
|
self.resource['project_id'] = None
|
|
|
|
mgr = self.index.get_resource_type_schema()
|
|
self.resource_type = str(uuid.uuid4())
|
|
self.index.create_resource_type(
|
|
mgr.resource_type_from_dict(self.resource_type, {
|
|
"name": {"type": "string",
|
|
"min_length": 1,
|
|
"max_length": 40,
|
|
"required": True}
|
|
}, "creating"))
|
|
self.resource['type'] = self.resource_type
|
|
|
|
@mock.patch.object(utils, 'utcnow')
|
|
def test_post_resource(self, utcnow):
|
|
utcnow.return_value = utils.datetime_utc(2014, 1, 1, 10, 23)
|
|
result = self.app.post_json(
|
|
"/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
resource = json.loads(result.text)
|
|
self.assertEqual("http://localhost/v1/resource/"
|
|
+ self.resource_type + "/" + self.attributes['id'],
|
|
result.headers['Location'])
|
|
self.assertIsNone(resource['revision_end'])
|
|
self.assertEqual(resource['revision_start'],
|
|
"2014-01-01T10:23:00+00:00")
|
|
self._check_etag(result, resource)
|
|
del resource['revision_start']
|
|
del resource['revision_end']
|
|
self.assertEqual(self.resource, resource)
|
|
|
|
def test_post_resource_with_invalid_metric(self):
|
|
metric_id = str(uuid.uuid4())
|
|
self.attributes['metrics'] = {"foo": metric_id}
|
|
result = self.app.post_json(
|
|
"/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=400)
|
|
self.assertIn("Metric %s does not exist" % metric_id,
|
|
result.text)
|
|
|
|
def test_post_resource_with_metric_from_other_user(self):
|
|
with self.app.use_another_user():
|
|
metric = self.app.post_json(
|
|
"/v1/metric",
|
|
params={'archive_policy_name': "high"})
|
|
metric_id = json.loads(metric.text)['id']
|
|
self.attributes['metrics'] = {"foo": metric_id}
|
|
result = self.app.post_json(
|
|
"/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=400)
|
|
self.assertIn("Metric %s does not exist" % metric_id,
|
|
result.text)
|
|
|
|
def test_post_resource_already_exist(self):
|
|
result = self.app.post_json(
|
|
"/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
result = self.app.post_json(
|
|
"/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=409)
|
|
self.assertIn("Resource %s already exists" % self.attributes['id'],
|
|
result.text)
|
|
|
|
def test_post_invalid_timestamp(self):
|
|
self.attributes['started_at'] = "2014-01-01 02:02:02"
|
|
self.attributes['ended_at'] = "2013-01-01 02:02:02"
|
|
self.app.post_json(
|
|
"/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=400)
|
|
|
|
@staticmethod
|
|
def _strtime_to_httpdate(dt):
|
|
return email_utils.formatdate(calendar.timegm(
|
|
timeutils.parse_isotime(dt).timetuple()), usegmt=True)
|
|
|
|
def _check_etag(self, response, resource):
|
|
lastmodified = self._strtime_to_httpdate(resource['revision_start'])
|
|
etag = hashlib.sha1()
|
|
etag.update(resource['id'].encode('utf-8'))
|
|
etag.update(resource['revision_start'].encode('utf8'))
|
|
self.assertEqual(response.headers['Last-Modified'], lastmodified)
|
|
self.assertEqual(response.headers['ETag'], '"%s"' % etag.hexdigest())
|
|
|
|
@mock.patch.object(utils, 'utcnow')
|
|
def test_get_resource(self, utcnow):
|
|
utcnow.return_value = utils.datetime_utc(2014, 1, 1, 10, 23)
|
|
result = self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
result = self.app.get("/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id'])
|
|
resource = json.loads(result.text)
|
|
self.assertIsNone(resource['revision_end'])
|
|
self.assertEqual(resource['revision_start'],
|
|
"2014-01-01T10:23:00+00:00")
|
|
self._check_etag(result, resource)
|
|
del resource['revision_start']
|
|
del resource['revision_end']
|
|
self.assertEqual(self.resource, resource)
|
|
|
|
def test_get_resource_etag(self):
|
|
result = self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
result = self.app.get("/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id'])
|
|
resource = json.loads(result.text)
|
|
etag = hashlib.sha1()
|
|
etag.update(resource['id'].encode('utf-8'))
|
|
etag.update(resource['revision_start'].encode('utf-8'))
|
|
etag = etag.hexdigest()
|
|
lastmodified = self._strtime_to_httpdate(resource['revision_start'])
|
|
oldlastmodified = self._strtime_to_httpdate("2000-01-01 00:00:00")
|
|
|
|
# if-match and if-unmodified-since
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-match': 'fake'},
|
|
status=412)
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-match': etag},
|
|
status=200)
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-unmodified-since': lastmodified},
|
|
status=200)
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-unmodified-since': oldlastmodified},
|
|
status=412)
|
|
# Some case with '*'
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-none-match': '*'},
|
|
status=304)
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/wrongid",
|
|
headers={'if-none-match': '*'},
|
|
status=404)
|
|
# always prefers if-match if both provided
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-match': etag,
|
|
'if-unmodified-since': lastmodified},
|
|
status=200)
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-match': etag,
|
|
'if-unmodified-since': oldlastmodified},
|
|
status=200)
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-match': '*',
|
|
'if-unmodified-since': oldlastmodified},
|
|
status=200)
|
|
|
|
# if-none-match and if-modified-since
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-none-match': etag},
|
|
status=304)
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-none-match': 'fake'},
|
|
status=200)
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-modified-since': lastmodified},
|
|
status=304)
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-modified-since': oldlastmodified},
|
|
status=200)
|
|
# always prefers if-none-match if both provided
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-modified-since': oldlastmodified,
|
|
'if-none-match': etag},
|
|
status=304)
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-modified-since': oldlastmodified,
|
|
'if-none-match': '*'},
|
|
status=304)
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-modified-since': lastmodified,
|
|
'if-none-match': '*'},
|
|
status=304)
|
|
# Some case with '*'
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-match': '*'},
|
|
status=200)
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/wrongid",
|
|
headers={'if-match': '*'},
|
|
status=404)
|
|
|
|
# if-none-match and if-match
|
|
self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-none-match': etag,
|
|
'if-match': etag},
|
|
status=304)
|
|
|
|
# if-none-match returns 412 instead 304 for PUT/PATCH/DELETE
|
|
self.app.patch_json("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-none-match': '*'},
|
|
status=412)
|
|
self.app.delete("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-none-match': '*'},
|
|
status=412)
|
|
|
|
# if-modified-since is ignored with PATCH/PUT/DELETE
|
|
self.app.patch_json("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
params=self.patchable_attributes,
|
|
headers={'if-modified-since': lastmodified},
|
|
status=200)
|
|
self.app.delete("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
headers={'if-modified-since': lastmodified},
|
|
status=204)
|
|
|
|
def test_get_resource_non_admin(self):
|
|
with self.app.use_another_user():
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
self.app.get("/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id'],
|
|
status=200)
|
|
|
|
def test_get_resource_unauthorized(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
with self.app.use_another_user():
|
|
self.app.get("/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id'],
|
|
status=403)
|
|
|
|
def test_get_resource_named_metric(self):
|
|
self.attributes['metrics'] = {'foo': {'archive_policy_name': "high"}}
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
self.app.get("/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id']
|
|
+ "/metric/foo/measures",
|
|
status=200)
|
|
|
|
def test_list_resource_metrics_unauthorized(self):
|
|
self.attributes['metrics'] = {'foo': {'archive_policy_name': "high"}}
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
with self.app.use_another_user():
|
|
self.app.get(
|
|
"/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'] + "/metric",
|
|
status=403)
|
|
|
|
def test_delete_resource_named_metric(self):
|
|
self.attributes['metrics'] = {'foo': {'archive_policy_name': "high"}}
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
self.app.delete("/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id']
|
|
+ "/metric/foo",
|
|
status=204)
|
|
self.app.delete("/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id']
|
|
+ "/metric/foo/measures",
|
|
status=404)
|
|
|
|
def test_get_resource_unknown_named_metric(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
self.app.get("/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id']
|
|
+ "/metric/foo",
|
|
status=404)
|
|
|
|
def test_post_append_metrics_already_exists(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
|
|
metrics = {'foo': {'archive_policy_name': "high"}}
|
|
self.app.post_json("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'] + "/metric",
|
|
params=metrics, status=204)
|
|
metrics = {'foo': {'archive_policy_name': "low"}}
|
|
self.app.post_json("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id']
|
|
+ "/metric",
|
|
params=metrics,
|
|
status=409)
|
|
|
|
result = self.app.get("/v1/resource/"
|
|
+ self.resource_type + "/"
|
|
+ self.attributes['id'])
|
|
result = json.loads(result.text)
|
|
self.assertTrue(uuid.UUID(result['metrics']['foo']))
|
|
|
|
def test_post_append_metrics(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
|
|
metrics = {'foo': {'archive_policy_name': "high"}}
|
|
self.app.post_json("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'] + "/metric",
|
|
params=metrics, status=204)
|
|
result = self.app.get("/v1/resource/"
|
|
+ self.resource_type + "/"
|
|
+ self.attributes['id'])
|
|
result = json.loads(result.text)
|
|
self.assertTrue(uuid.UUID(result['metrics']['foo']))
|
|
|
|
def test_post_append_metrics_created_by_different_user(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
with self.app.use_another_user():
|
|
metric = self.app.post_json(
|
|
"/v1/metric",
|
|
params={'archive_policy_name': "high"})
|
|
metric_id = json.loads(metric.text)['id']
|
|
result = self.app.post_json("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'] + "/metric",
|
|
params={str(uuid.uuid4()): metric_id},
|
|
status=400)
|
|
self.assertIn("Metric %s does not exist" % metric_id, result.text)
|
|
|
|
@mock.patch.object(utils, 'utcnow')
|
|
def test_patch_resource_metrics(self, utcnow):
|
|
utcnow.return_value = utils.datetime_utc(2014, 1, 1, 10, 23)
|
|
result = self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
r = json.loads(result.text)
|
|
|
|
utcnow.return_value = utils.datetime_utc(2014, 1, 2, 6, 49)
|
|
new_metrics = {'foo': {'archive_policy_name': "medium"}}
|
|
self.app.patch_json(
|
|
"/v1/resource/" + self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
params={'metrics': new_metrics},
|
|
status=200)
|
|
result = self.app.get("/v1/resource/"
|
|
+ self.resource_type + "/"
|
|
+ self.attributes['id'])
|
|
result = json.loads(result.text)
|
|
self.assertTrue(uuid.UUID(result['metrics']['foo']))
|
|
self.assertIsNone(result['revision_end'])
|
|
self.assertIsNone(r['revision_end'])
|
|
self.assertEqual(result['revision_start'], "2014-01-01T10:23:00+00:00")
|
|
self.assertEqual(r['revision_start'], "2014-01-01T10:23:00+00:00")
|
|
|
|
del result['metrics']
|
|
del result['revision_start']
|
|
del result['revision_end']
|
|
del r['metrics']
|
|
del r['revision_start']
|
|
del r['revision_end']
|
|
self.assertEqual(r, result)
|
|
|
|
def test_patch_resource_existent_metrics_from_another_user(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
with self.app.use_another_user():
|
|
result = self.app.post_json(
|
|
"/v1/metric",
|
|
params={'archive_policy_name': "medium"})
|
|
metric_id = json.loads(result.text)['id']
|
|
result = self.app.patch_json(
|
|
"/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id'],
|
|
params={'metrics': {'foo': metric_id}},
|
|
status=400)
|
|
self.assertIn("Metric %s does not exist" % metric_id, result.text)
|
|
result = self.app.get("/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id'])
|
|
result = json.loads(result.text)
|
|
self.assertEqual({}, result['metrics'])
|
|
|
|
def test_patch_resource_non_existent_metrics(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
e1 = str(uuid.uuid4())
|
|
result = self.app.patch_json(
|
|
"/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id'],
|
|
params={'metrics': {'foo': e1}},
|
|
status=400)
|
|
self.assertIn("Metric %s does not exist" % e1, result.text)
|
|
result = self.app.get("/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id'])
|
|
result = json.loads(result.text)
|
|
self.assertEqual({}, result['metrics'])
|
|
|
|
@mock.patch.object(utils, 'utcnow')
|
|
def test_patch_resource_attributes(self, utcnow):
|
|
utcnow.return_value = utils.datetime_utc(2014, 1, 1, 10, 23)
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
utcnow.return_value = utils.datetime_utc(2014, 1, 2, 6, 48)
|
|
presponse = self.app.patch_json(
|
|
"/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
params=self.patchable_attributes,
|
|
status=200)
|
|
response = self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'])
|
|
result = json.loads(response.text)
|
|
presult = json.loads(presponse.text)
|
|
self.assertEqual(result, presult)
|
|
for k, v in six.iteritems(self.patchable_attributes):
|
|
self.assertEqual(v, result[k])
|
|
self.assertIsNone(result['revision_end'])
|
|
self.assertEqual(result['revision_start'],
|
|
"2014-01-02T06:48:00+00:00")
|
|
self._check_etag(response, result)
|
|
|
|
# Check the history
|
|
history = self.app.post_json(
|
|
"/v1/search/resource/" + self.resource_type,
|
|
headers={"Accept": "application/json; history=true"},
|
|
params={"=": {"id": result['id']}},
|
|
status=200)
|
|
history = json.loads(history.text)
|
|
self.assertGreaterEqual(len(history), 2)
|
|
self.assertEqual(result, history[1])
|
|
|
|
h = history[0]
|
|
for k, v in six.iteritems(self.attributes):
|
|
self.assertEqual(v, h[k])
|
|
self.assertEqual(h['revision_end'],
|
|
"2014-01-02T06:48:00+00:00")
|
|
self.assertEqual(h['revision_start'],
|
|
"2014-01-01T10:23:00+00:00")
|
|
|
|
def test_patch_resource_attributes_unauthorized(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
with self.app.use_another_user():
|
|
self.app.patch_json(
|
|
"/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'],
|
|
params=self.patchable_attributes,
|
|
status=403)
|
|
|
|
def test_patch_resource_ended_at_before_started_at(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
self.app.patch_json(
|
|
"/v1/resource/"
|
|
+ self.resource_type
|
|
+ "/"
|
|
+ self.attributes['id'],
|
|
params={'ended_at': "2000-05-05 23:23:23"},
|
|
status=400)
|
|
|
|
def test_patch_resource_no_partial_update(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
e1 = str(uuid.uuid4())
|
|
result = self.app.patch_json(
|
|
"/v1/resource/" + self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
params={'ended_at': "2044-05-05 23:23:23",
|
|
'metrics': {"foo": e1}},
|
|
status=400)
|
|
self.assertIn("Metric %s does not exist" % e1, result.text)
|
|
result = self.app.get("/v1/resource/"
|
|
+ self.resource_type + "/"
|
|
+ self.attributes['id'])
|
|
result = json.loads(result.text)
|
|
del result['revision_start']
|
|
del result['revision_end']
|
|
self.assertEqual(self.resource, result)
|
|
|
|
def test_patch_resource_non_existent(self):
|
|
self.app.patch_json(
|
|
"/v1/resource/" + self.resource_type
|
|
+ "/" + str(uuid.uuid4()),
|
|
params={},
|
|
status=404)
|
|
|
|
def test_patch_resource_non_existent_with_body(self):
|
|
self.app.patch_json(
|
|
"/v1/resource/" + self.resource_type
|
|
+ "/" + str(uuid.uuid4()),
|
|
params=self.patchable_attributes,
|
|
status=404)
|
|
|
|
def test_patch_resource_unknown_field(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
result = self.app.patch_json(
|
|
"/v1/resource/" + self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
params={'foobar': 123},
|
|
status=400)
|
|
self.assertIn(b'Invalid input: extra keys not allowed @ data['
|
|
+ repr(u'foobar').encode('ascii') + b"]",
|
|
result.body)
|
|
|
|
def test_delete_resource(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
self.app.get("/v1/resource/" + self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
status=200)
|
|
self.app.delete("/v1/resource/" + self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
status=204)
|
|
self.app.get("/v1/resource/" + self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
status=404)
|
|
|
|
def test_delete_resource_with_metrics(self):
|
|
metric = self.app.post_json(
|
|
"/v1/metric",
|
|
params={'archive_policy_name': "high"})
|
|
metric_id = json.loads(metric.text)['id']
|
|
metric_name = six.text_type(uuid.uuid4())
|
|
self.attributes['metrics'] = {metric_name: metric_id}
|
|
self.app.get("/v1/metric/" + metric_id,
|
|
status=200)
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
self.app.get("/v1/resource/" + self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
status=200)
|
|
self.app.delete("/v1/resource/" + self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
status=204)
|
|
self.app.get("/v1/resource/" + self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
status=404)
|
|
self.app.get("/v1/metric/" + metric_id,
|
|
status=404)
|
|
|
|
def test_delete_resource_unauthorized(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
with self.app.use_another_user():
|
|
self.app.delete("/v1/resource/" + self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
status=403)
|
|
|
|
def test_delete_resource_non_existent(self):
|
|
result = self.app.delete("/v1/resource/" + self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
status=404)
|
|
self.assertIn(
|
|
"Resource %s does not exist" % self.attributes['id'],
|
|
result.text)
|
|
|
|
def test_post_resource_with_metrics(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric = json.loads(result.text)
|
|
self.attributes['metrics'] = {"foo": metric['id']}
|
|
result = self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
resource = json.loads(result.text)
|
|
self.assertEqual("http://localhost/v1/resource/"
|
|
+ self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
result.headers['Location'])
|
|
self.resource['metrics'] = self.attributes['metrics']
|
|
del resource['revision_start']
|
|
del resource['revision_end']
|
|
self.assertEqual(self.resource, resource)
|
|
|
|
def test_post_resource_with_null_metrics(self):
|
|
self.attributes['metrics'] = {"foo": {"archive_policy_name": "low"}}
|
|
result = self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
resource = json.loads(result.text)
|
|
self.assertEqual("http://localhost/v1/resource/"
|
|
+ self.resource_type + "/"
|
|
+ self.attributes['id'],
|
|
result.headers['Location'])
|
|
self.assertEqual(self.attributes['id'], resource["id"])
|
|
metric_id = uuid.UUID(resource['metrics']['foo'])
|
|
result = self.app.get("/v1/metric/" + str(metric_id) + "/measures",
|
|
status=200)
|
|
|
|
def test_search_datetime(self):
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes,
|
|
status=201)
|
|
result = self.app.get("/v1/resource/" + self.resource_type
|
|
+ "/" + self.attributes['id'])
|
|
result = json.loads(result.text)
|
|
|
|
resources = self.app.post_json(
|
|
"/v1/search/resource/" + self.resource_type,
|
|
params={"and": [{"=": {"id": result['id']}},
|
|
{"=": {"ended_at": None}}]},
|
|
status=200)
|
|
resources = json.loads(resources.text)
|
|
self.assertGreaterEqual(len(resources), 1)
|
|
self.assertEqual(result, resources[0])
|
|
|
|
resources = self.app.post_json(
|
|
"/v1/search/resource/" + self.resource_type,
|
|
headers={"Accept": "application/json; history=true"},
|
|
params={"and": [
|
|
{"=": {"id": result['id']}},
|
|
{"or": [{">=": {"revision_end": '2014-01-03T02:02:02'}},
|
|
{"=": {"revision_end": None}}]}
|
|
]},
|
|
status=200)
|
|
resources = json.loads(resources.text)
|
|
self.assertGreaterEqual(len(resources), 1)
|
|
self.assertEqual(result, resources[0])
|
|
|
|
def test_search_resource_by_original_resource_id(self):
|
|
result = self.app.post_json(
|
|
"/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
created_resource = json.loads(result.text)
|
|
original_id = created_resource['original_resource_id']
|
|
result = self.app.post_json(
|
|
"/v1/search/resource/" + self.resource_type,
|
|
params={"eq": {"original_resource_id": original_id}},
|
|
status=200)
|
|
resources = json.loads(result.text)
|
|
self.assertGreaterEqual(len(resources), 1)
|
|
self.assertEqual(created_resource, resources[0])
|
|
|
|
def test_search_resources_by_user(self):
|
|
u1 = str(uuid.uuid4())
|
|
self.attributes['user_id'] = u1
|
|
result = self.app.post_json(
|
|
"/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
created_resource = json.loads(result.text)
|
|
result = self.app.post_json("/v1/search/resource/generic",
|
|
params={"eq": {"user_id": u1}},
|
|
status=200)
|
|
resources = json.loads(result.text)
|
|
self.assertGreaterEqual(len(resources), 1)
|
|
result = self.app.post_json(
|
|
"/v1/search/resource/" + self.resource_type,
|
|
params={"=": {"user_id": u1}},
|
|
status=200)
|
|
resources = json.loads(result.text)
|
|
self.assertGreaterEqual(len(resources), 1)
|
|
self.assertEqual(created_resource, resources[0])
|
|
|
|
def test_search_resources_with_another_project_id(self):
|
|
u1 = str(uuid.uuid4())
|
|
result = self.app.post_json(
|
|
"/v1/resource/generic",
|
|
params={
|
|
"id": str(uuid.uuid4()),
|
|
"started_at": "2014-01-01 02:02:02",
|
|
"user_id": u1,
|
|
"project_id": TestingApp.PROJECT_ID_2,
|
|
})
|
|
g = json.loads(result.text)
|
|
|
|
with self.app.use_another_user():
|
|
result = self.app.post_json(
|
|
"/v1/resource/generic",
|
|
params={
|
|
"id": str(uuid.uuid4()),
|
|
"started_at": "2014-01-01 03:03:03",
|
|
"user_id": u1,
|
|
"project_id": str(uuid.uuid4()),
|
|
})
|
|
j = json.loads(result.text)
|
|
g_found = False
|
|
j_found = False
|
|
|
|
result = self.app.post_json(
|
|
"/v1/search/resource/generic",
|
|
params={"=": {"user_id": u1}},
|
|
status=200)
|
|
resources = json.loads(result.text)
|
|
self.assertGreaterEqual(len(resources), 2)
|
|
for r in resources:
|
|
if r['id'] == str(g['id']):
|
|
self.assertEqual(g, r)
|
|
g_found = True
|
|
elif r['id'] == str(j['id']):
|
|
self.assertEqual(j, r)
|
|
j_found = True
|
|
if g_found and j_found:
|
|
break
|
|
else:
|
|
self.fail("Some resources were not found")
|
|
|
|
def test_search_resources_by_unknown_field(self):
|
|
result = self.app.post_json(
|
|
"/v1/search/resource/" + self.resource_type,
|
|
params={"=": {"foobar": "baz"}},
|
|
status=400)
|
|
self.assertIn("Resource type " + self.resource_type
|
|
+ " has no foobar attribute",
|
|
result.text)
|
|
|
|
def test_search_resources_started_after(self):
|
|
# NOTE(jd) So this test is a bit fuzzy right now as we uses the same
|
|
# database for all tests and the tests are running concurrently, but
|
|
# for now it'll be better than nothing.
|
|
result = self.app.post_json(
|
|
"/v1/resource/generic/",
|
|
params={
|
|
"id": str(uuid.uuid4()),
|
|
"started_at": "2014-01-01 02:02:02",
|
|
"user_id": str(uuid.uuid4()),
|
|
"project_id": str(uuid.uuid4()),
|
|
})
|
|
g = json.loads(result.text)
|
|
result = self.app.post_json(
|
|
"/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
i = json.loads(result.text)
|
|
result = self.app.post_json(
|
|
"/v1/search/resource/generic",
|
|
params={"≥": {"started_at": "2014-01-01"}},
|
|
status=200)
|
|
resources = json.loads(result.text)
|
|
self.assertGreaterEqual(len(resources), 2)
|
|
|
|
i_found = False
|
|
g_found = False
|
|
for r in resources:
|
|
if r['id'] == str(g['id']):
|
|
self.assertEqual(g, r)
|
|
g_found = True
|
|
elif r['id'] == str(i['id']):
|
|
i_found = True
|
|
if i_found and g_found:
|
|
break
|
|
else:
|
|
self.fail("Some resources were not found")
|
|
|
|
result = self.app.post_json(
|
|
"/v1/search/resource/" + self.resource_type,
|
|
params={">=": {"started_at": "2014-01-03"}})
|
|
resources = json.loads(result.text)
|
|
self.assertGreaterEqual(len(resources), 1)
|
|
for r in resources:
|
|
if r['id'] == str(i['id']):
|
|
self.assertEqual(i, r)
|
|
break
|
|
else:
|
|
self.fail("Some resources were not found")
|
|
|
|
def test_list_resources_with_bad_details(self):
|
|
result = self.app.get("/v1/resource/generic?details=awesome",
|
|
status=400)
|
|
self.assertIn(
|
|
b"Unable to parse details value in query: "
|
|
b"Unrecognized value 'awesome', acceptable values are",
|
|
result.body)
|
|
|
|
def test_list_resources_with_bad_details_in_accept(self):
|
|
result = self.app.get("/v1/resource/generic",
|
|
headers={
|
|
"Accept": "application/json; details=foo",
|
|
},
|
|
status=400)
|
|
self.assertIn(
|
|
b"Unable to parse details value in Accept: "
|
|
b"Unrecognized value 'foo', acceptable values are",
|
|
result.body)
|
|
|
|
def _do_test_list_resources_with_detail(self, request):
|
|
# NOTE(jd) So this test is a bit fuzzy right now as we uses the same
|
|
# database for all tests and the tests are running concurrently, but
|
|
# for now it'll be better than nothing.
|
|
result = self.app.post_json(
|
|
"/v1/resource/generic",
|
|
params={
|
|
"id": str(uuid.uuid4()),
|
|
"started_at": "2014-01-01 02:02:02",
|
|
"user_id": str(uuid.uuid4()),
|
|
"project_id": str(uuid.uuid4()),
|
|
})
|
|
g = json.loads(result.text)
|
|
result = self.app.post_json(
|
|
"/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
i = json.loads(result.text)
|
|
result = request()
|
|
self.assertEqual(200, result.status_code)
|
|
resources = json.loads(result.text)
|
|
self.assertGreaterEqual(len(resources), 2)
|
|
|
|
i_found = False
|
|
g_found = False
|
|
for r in resources:
|
|
if r['id'] == str(g['id']):
|
|
self.assertEqual(g, r)
|
|
g_found = True
|
|
elif r['id'] == str(i['id']):
|
|
i_found = True
|
|
# Check we got all the details
|
|
self.assertEqual(i, r)
|
|
if i_found and g_found:
|
|
break
|
|
else:
|
|
self.fail("Some resources were not found")
|
|
|
|
result = self.app.get("/v1/resource/" + self.resource_type)
|
|
resources = json.loads(result.text)
|
|
self.assertGreaterEqual(len(resources), 1)
|
|
for r in resources:
|
|
if r['id'] == str(i['id']):
|
|
self.assertEqual(i, r)
|
|
break
|
|
else:
|
|
self.fail("Some resources were not found")
|
|
|
|
def test_list_resources_with_another_project_id(self):
|
|
result = self.app.post_json(
|
|
"/v1/resource/generic",
|
|
params={
|
|
"id": str(uuid.uuid4()),
|
|
"started_at": "2014-01-01 02:02:02",
|
|
"user_id": TestingApp.USER_ID_2,
|
|
"project_id": TestingApp.PROJECT_ID_2,
|
|
})
|
|
g = json.loads(result.text)
|
|
|
|
with self.app.use_another_user():
|
|
result = self.app.post_json(
|
|
"/v1/resource/generic",
|
|
params={
|
|
"id": str(uuid.uuid4()),
|
|
"started_at": "2014-01-01 03:03:03",
|
|
"user_id": str(uuid.uuid4()),
|
|
"project_id": str(uuid.uuid4()),
|
|
})
|
|
j = json.loads(result.text)
|
|
|
|
g_found = False
|
|
j_found = False
|
|
|
|
result = self.app.get("/v1/resource/generic")
|
|
self.assertEqual(200, result.status_code)
|
|
resources = json.loads(result.text)
|
|
self.assertGreaterEqual(len(resources), 2)
|
|
for r in resources:
|
|
if r['id'] == str(g['id']):
|
|
self.assertEqual(g, r)
|
|
g_found = True
|
|
elif r['id'] == str(j['id']):
|
|
self.assertEqual(j, r)
|
|
j_found = True
|
|
if g_found and j_found:
|
|
break
|
|
else:
|
|
self.fail("Some resources were not found")
|
|
|
|
def test_list_resources_with_details(self):
|
|
self._do_test_list_resources_with_detail(
|
|
lambda: self.app.get("/v1/resource/generic?details=true"))
|
|
|
|
def test_list_resources_with_details_via_accept(self):
|
|
self._do_test_list_resources_with_detail(
|
|
lambda: self.app.get(
|
|
"/v1/resource/generic",
|
|
headers={"Accept": "application/json; details=true"}))
|
|
|
|
def test_search_resources_with_details(self):
|
|
self._do_test_list_resources_with_detail(
|
|
lambda: self.app.post("/v1/search/resource/generic?details=true"))
|
|
|
|
def test_search_resources_with_details_via_accept(self):
|
|
self._do_test_list_resources_with_detail(
|
|
lambda: self.app.post(
|
|
"/v1/search/resource/generic",
|
|
headers={"Accept": "application/json; details=true"}))
|
|
|
|
def test_get_res_named_metric_measure_aggregated_policies_invalid(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "low"})
|
|
metric1 = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric1['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:01',
|
|
"value": 16}])
|
|
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name":
|
|
"no_granularity_match"})
|
|
metric2 = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric2['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:01',
|
|
"value": 4}])
|
|
|
|
# NOTE(sileht): because the database is never cleaned between each test
|
|
# we must ensure that the query will not match resources from an other
|
|
# test, to achieve this we set a different name on each test.
|
|
name = str(uuid.uuid4())
|
|
self.attributes['name'] = name
|
|
|
|
self.attributes['metrics'] = {'foo': metric1['id']}
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
|
|
self.attributes['id'] = str(uuid.uuid4())
|
|
self.attributes['metrics'] = {'foo': metric2['id']}
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
|
|
result = self.app.post_json(
|
|
"/v1/aggregation/resource/"
|
|
+ self.resource_type + "/metric/foo?aggregation=max",
|
|
params={"=": {"name": name}},
|
|
status=400)
|
|
self.assertIn(b"One of the metrics being aggregated doesn't have "
|
|
b"matching granularity",
|
|
result.body)
|
|
|
|
def test_get_res_named_metric_measure_aggregation_nooverlap(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric1 = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric1['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:01',
|
|
"value": 8},
|
|
{"timestamp": '2013-01-01 12:00:02',
|
|
"value": 16}])
|
|
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric2 = json.loads(result.text)
|
|
|
|
# NOTE(sileht): because the database is never cleaned between each test
|
|
# we must ensure that the query will not match resources from an other
|
|
# test, to achieve this we set a different name on each test.
|
|
name = str(uuid.uuid4())
|
|
self.attributes['name'] = name
|
|
|
|
self.attributes['metrics'] = {'foo': metric1['id']}
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
|
|
self.attributes['id'] = str(uuid.uuid4())
|
|
self.attributes['metrics'] = {'foo': metric2['id']}
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
|
|
result = self.app.post_json(
|
|
"/v1/aggregation/resource/" + self.resource_type
|
|
+ "/metric/foo?aggregation=max",
|
|
params={"=": {"name": name}},
|
|
expect_errors=True)
|
|
|
|
self.assertEqual(400, result.status_code, result.text)
|
|
self.assertIn("No overlap", result.text)
|
|
|
|
result = self.app.post_json(
|
|
"/v1/aggregation/resource/"
|
|
+ self.resource_type + "/metric/foo?aggregation=min"
|
|
+ "&needed_overlap=0",
|
|
params={"=": {"name": name}},
|
|
expect_errors=True)
|
|
|
|
self.assertEqual(200, result.status_code, result.text)
|
|
measures = json.loads(result.text)
|
|
self.assertEqual([['2013-01-01T00:00:00+00:00', 86400.0, 8.0],
|
|
['2013-01-01T12:00:00+00:00', 3600.0, 8.0],
|
|
['2013-01-01T12:00:00+00:00', 60.0, 8.0]],
|
|
measures)
|
|
|
|
def test_get_res_named_metric_measure_aggregation_nominal(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric1 = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric1['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:01',
|
|
"value": 8},
|
|
{"timestamp": '2013-01-01 12:00:02',
|
|
"value": 16}])
|
|
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric2 = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric2['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:01',
|
|
"value": 0},
|
|
{"timestamp": '2013-01-01 12:00:02',
|
|
"value": 4}])
|
|
|
|
# NOTE(sileht): because the database is never cleaned between each test
|
|
# we must ensure that the query will not match resources from an other
|
|
# test, to achieve this we set a different name on each test.
|
|
name = str(uuid.uuid4())
|
|
self.attributes['name'] = name
|
|
|
|
self.attributes['metrics'] = {'foo': metric1['id']}
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
|
|
self.attributes['id'] = str(uuid.uuid4())
|
|
self.attributes['metrics'] = {'foo': metric2['id']}
|
|
self.app.post_json("/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
|
|
result = self.app.post_json(
|
|
"/v1/aggregation/resource/" + self.resource_type
|
|
+ "/metric/foo?aggregation=max",
|
|
params={"=": {"name": name}},
|
|
expect_errors=True)
|
|
|
|
self.assertEqual(200, result.status_code, result.text)
|
|
measures = json.loads(result.text)
|
|
self.assertEqual([[u'2013-01-01T00:00:00+00:00', 86400.0, 16.0],
|
|
[u'2013-01-01T12:00:00+00:00', 3600.0, 16.0],
|
|
[u'2013-01-01T12:00:00+00:00', 60.0, 16.0]],
|
|
measures)
|
|
|
|
result = self.app.post_json(
|
|
"/v1/aggregation/resource/"
|
|
+ self.resource_type + "/metric/foo?aggregation=min",
|
|
params={"=": {"name": name}},
|
|
expect_errors=True)
|
|
|
|
self.assertEqual(200, result.status_code)
|
|
measures = json.loads(result.text)
|
|
self.assertEqual([['2013-01-01T00:00:00+00:00', 86400.0, 0],
|
|
['2013-01-01T12:00:00+00:00', 3600.0, 0],
|
|
['2013-01-01T12:00:00+00:00', 60.0, 0]],
|
|
measures)
|
|
|
|
def test_get_aggregated_measures_across_entities_no_match(self):
|
|
result = self.app.post_json(
|
|
"/v1/aggregation/resource/"
|
|
+ self.resource_type + "/metric/foo?aggregation=min",
|
|
params={"=": {"name": "none!"}},
|
|
expect_errors=True)
|
|
|
|
self.assertEqual(200, result.status_code)
|
|
measures = json.loads(result.text)
|
|
self.assertEqual([], measures)
|
|
|
|
def test_get_aggregated_measures_across_entities(self):
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric1 = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric1['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:01',
|
|
"value": 8},
|
|
{"timestamp": '2013-01-01 12:00:02',
|
|
"value": 16}])
|
|
|
|
result = self.app.post_json("/v1/metric",
|
|
params={"archive_policy_name": "medium"})
|
|
metric2 = json.loads(result.text)
|
|
self.app.post_json("/v1/metric/%s/measures" % metric2['id'],
|
|
params=[{"timestamp": '2013-01-01 12:00:01',
|
|
"value": 0},
|
|
{"timestamp": '2013-01-01 12:00:02',
|
|
"value": 4}])
|
|
# Check with one metric
|
|
result = self.app.get("/v1/aggregation/metric"
|
|
"?aggregation=mean&metric=%s" % (metric2['id']))
|
|
measures = json.loads(result.text)
|
|
self.assertEqual([[u'2013-01-01T00:00:00+00:00', 86400.0, 2.0],
|
|
[u'2013-01-01T12:00:00+00:00', 3600.0, 2.0],
|
|
[u'2013-01-01T12:00:00+00:00', 60.0, 2.0]],
|
|
measures)
|
|
|
|
# Check with two metrics
|
|
result = self.app.get("/v1/aggregation/metric"
|
|
"?aggregation=mean&metric=%s&metric=%s" %
|
|
(metric1['id'], metric2['id']))
|
|
measures = json.loads(result.text)
|
|
self.assertEqual([[u'2013-01-01T00:00:00+00:00', 86400.0, 7.0],
|
|
[u'2013-01-01T12:00:00+00:00', 3600.0, 7.0],
|
|
[u'2013-01-01T12:00:00+00:00', 60.0, 7.0]],
|
|
measures)
|
|
|
|
def test_search_resources_with_like(self):
|
|
result = self.app.post_json(
|
|
"/v1/resource/" + self.resource_type,
|
|
params=self.attributes)
|
|
created_resource = json.loads(result.text)
|
|
|
|
result = self.app.post_json(
|
|
"/v1/search/resource/" + self.resource_type,
|
|
params={"like": {"name": "my%"}},
|
|
status=200)
|
|
|
|
resources = json.loads(result.text)
|
|
self.assertIn(created_resource, resources)
|
|
|
|
result = self.app.post_json(
|
|
"/v1/search/resource/" + self.resource_type,
|
|
params={"like": {"name": str(uuid.uuid4())}},
|
|
status=200)
|
|
resources = json.loads(result.text)
|
|
self.assertEqual([], resources)
|
|
|
|
|
|
class GenericResourceTest(RestTest):
|
|
def test_list_resources_tied_to_user(self):
|
|
resource_id = str(uuid.uuid4())
|
|
self.app.post_json(
|
|
"/v1/resource/generic",
|
|
params={
|
|
"id": resource_id,
|
|
"started_at": "2014-01-01 02:02:02",
|
|
"user_id": str(uuid.uuid4()),
|
|
"project_id": str(uuid.uuid4()),
|
|
})
|
|
|
|
with self.app.use_another_user():
|
|
result = self.app.get("/v1/resource/generic")
|
|
resources = json.loads(result.text)
|
|
for resource in resources:
|
|
if resource['id'] == resource_id:
|
|
self.fail("Resource found")
|
|
|
|
def test_get_resources_metric_tied_to_user(self):
|
|
resource_id = str(uuid.uuid4())
|
|
self.app.post_json(
|
|
"/v1/resource/generic",
|
|
params={
|
|
"id": resource_id,
|
|
"started_at": "2014-01-01 02:02:02",
|
|
"user_id": TestingApp.USER_ID_2,
|
|
"project_id": TestingApp.PROJECT_ID_2,
|
|
"metrics": {"foobar": {"archive_policy_name": "low"}},
|
|
})
|
|
|
|
# This user created it, she can access it
|
|
self.app.get(
|
|
"/v1/resource/generic/%s/metric/foobar" % resource_id)
|
|
|
|
with self.app.use_another_user():
|
|
# This user "owns it", it should be able to access it
|
|
self.app.get(
|
|
"/v1/resource/generic/%s/metric/foobar" % resource_id)
|
|
|
|
def test_search_resources_invalid_query(self):
|
|
result = self.app.post_json(
|
|
"/v1/search/resource/generic",
|
|
params={"wrongoperator": {"user_id": "bar"}},
|
|
status=400)
|
|
self.assertIn(
|
|
"Invalid input: extra keys not allowed @ data["
|
|
+ repr(u'wrongoperator') + "]",
|
|
result.text)
|
|
|
|
|
|
class QueryStringSearchAttrFilterTest(tests_base.TestCase):
|
|
def _do_test(self, expr, expected):
|
|
req = rest.QueryStringSearchAttrFilter.parse(expr)
|
|
self.assertEqual(expected, req)
|
|
|
|
def test_search_query_builder(self):
|
|
self._do_test('foo=7EED6CC3-EDC8-48C9-8EF6-8A36B9ACC91C',
|
|
{"=": {"foo": "7EED6CC3-EDC8-48C9-8EF6-8A36B9ACC91C"}})
|
|
self._do_test('foo=7EED6CC3EDC848C98EF68A36B9ACC91C',
|
|
{"=": {"foo": "7EED6CC3EDC848C98EF68A36B9ACC91C"}})
|
|
self._do_test('foo=bar', {"=": {"foo": "bar"}})
|
|
self._do_test('foo!=1', {"!=": {"foo": 1.0}})
|
|
self._do_test('foo=True', {"=": {"foo": True}})
|
|
self._do_test('foo=null', {"=": {"foo": None}})
|
|
self._do_test('foo="null"', {"=": {"foo": "null"}})
|
|
self._do_test('foo in ["null", "foo"]',
|
|
{"in": {"foo": ["null", "foo"]}})
|
|
self._do_test(u'foo="quote" and bar≠1',
|
|
{"and": [{u"≠": {"bar": 1}},
|
|
{"=": {"foo": "quote"}}]})
|
|
self._do_test('foo="quote" or bar like "%%foo"',
|
|
{"or": [{"like": {"bar": "%%foo"}},
|
|
{"=": {"foo": "quote"}}]})
|
|
|
|
self._do_test('not (foo="quote" or bar like "%%foo" or foo="what!" '
|
|
'or bar="who?")',
|
|
{"not": {"or": [
|
|
{"=": {"bar": "who?"}},
|
|
{"=": {"foo": "what!"}},
|
|
{"like": {"bar": "%%foo"}},
|
|
{"=": {"foo": "quote"}},
|
|
]}})
|
|
|
|
self._do_test('(foo="quote" or bar like "%%foo" or not foo="what!" '
|
|
'or bar="who?") and cat="meme"',
|
|
{"and": [
|
|
{"=": {"cat": "meme"}},
|
|
{"or": [
|
|
{"=": {"bar": "who?"}},
|
|
{"not": {"=": {"foo": "what!"}}},
|
|
{"like": {"bar": "%%foo"}},
|
|
{"=": {"foo": "quote"}},
|
|
]}
|
|
]})
|
|
|
|
self._do_test('foo="quote" or bar like "%%foo" or foo="what!" '
|
|
'or bar="who?" and cat="meme"',
|
|
{"or": [
|
|
{"and": [
|
|
{"=": {"cat": "meme"}},
|
|
{"=": {"bar": "who?"}},
|
|
]},
|
|
{"=": {"foo": "what!"}},
|
|
{"like": {"bar": "%%foo"}},
|
|
{"=": {"foo": "quote"}},
|
|
]})
|
|
|
|
self._do_test('foo="quote" or bar like "%%foo" and foo="what!" '
|
|
'or bar="who?" or cat="meme"',
|
|
{"or": [
|
|
{"=": {"cat": "meme"}},
|
|
{"=": {"bar": "who?"}},
|
|
{"and": [
|
|
{"=": {"foo": "what!"}},
|
|
{"like": {"bar": "%%foo"}},
|
|
]},
|
|
{"=": {"foo": "quote"}},
|
|
]})
|