blob: efd671e797f37bef09b3c02af9a825adb1dcd5d0 [file] [log] [blame]
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import absolute_import
import datetime
import json
import unittest
import os
import gae_ts_mon
import mock
import webapp2
from google.appengine.api.runtime import runtime
from .test_support import test_case
from infra_libs.ts_mon import config
from infra_libs.ts_mon import handlers
from infra_libs.ts_mon import shared
from infra_libs.ts_mon.common import interface
from infra_libs.ts_mon.common import metrics
from infra_libs.ts_mon.common import targets
class HelperFunctionsTest(unittest.TestCase):
def test_find_gaps(self):
self.assertEqual(
list(zip(range(5), handlers.find_gaps([1, 3, 5]))),
list(enumerate([0, 2, 4, 6, 7])))
self.assertEqual(
list(zip(range(5), handlers.find_gaps([0, 1, 2, 3, 5]))),
list(enumerate([4, 6, 7, 8, 9])))
self.assertEqual(
list(zip(range(3), handlers.find_gaps([]))), list(enumerate([0, 1, 2])))
self.assertEqual(
list(zip(range(3), handlers.find_gaps([2]))), list(
enumerate([0, 1, 3])))
class HandlersTest(test_case.TestCase):
def setUp(self):
super(HandlersTest, self).setUp()
config.reset_for_unittest()
target = targets.TaskTarget(
'test_service', 'test_job', 'test_region', 'test_host')
self.mock_state = interface.State(target=target)
mock.patch('infra_libs.ts_mon.common.interface.state',
new=self.mock_state).start()
def tearDown(self):
mock.patch.stopall()
config.reset_for_unittest()
super(HandlersTest, self).tearDown()
def test_assign_task_num(self):
time_now = datetime.datetime(2016, 2, 8, 1, 0, 0)
time_current = time_now - datetime.timedelta(
seconds=shared.INSTANCE_EXPIRE_SEC-1)
time_expired = time_now - datetime.timedelta(
seconds=shared.INSTANCE_EXPIRE_SEC+1)
with shared.instance_namespace_context():
shared.Instance(id='expired', task_num=0, last_updated=time_expired).put()
shared.Instance(id='inactive', task_num=-1, last_updated=time_expired).put()
shared.Instance(id='new', task_num=-1, last_updated=time_current).put()
shared.Instance(id='current', task_num=2, last_updated=time_current).put()
handlers._assign_task_num(time_fn=lambda: time_now)
expired = shared.Instance.get_by_id('expired')
inactive = shared.Instance.get_by_id('inactive')
new = shared.Instance.get_by_id('new')
current = shared.Instance.get_by_id('current')
self.assertIsNone(expired)
self.assertIsNone(inactive)
self.assertIsNotNone(new)
self.assertIsNotNone(current)
self.assertEqual(2, current.task_num)
self.assertEqual(1, new.task_num)
class TSMonJSHandlerTest(test_case.TestCase):
def setUp(self):
super(TSMonJSHandlerTest, self).setUp()
config.reset_for_unittest()
target = targets.TaskTarget(
'test_service', 'test_job', 'test_region', 'test_host')
self.mock_state = interface.State(target=target)
mock.patch('infra_libs.ts_mon.common.interface.state',
new=self.mock_state).start()
self.request = webapp2.Request.blank('/_/ts_mon_js')
self.response = webapp2.Response()
self.ts_mon_handler = handlers.TSMonJSHandler(
request=self.request, response=self.response)
self.ts_mon_handler.register_metrics([
metrics.BooleanMetric(
'frontend/boolean_test', 'Boolean metric test',
field_spec=[metrics.StringField('client_id')]),
])
self.ts_mon_handler.xsrf_is_valid = mock.Mock(return_value=True)
self.mock_timestamp = 1537821859
def time_fn():
return self.mock_timestamp
self.ts_mon_handler.time_fn = time_fn
def tearDown(self):
mock.patch.stopall()
config.reset_for_unittest()
super(TSMonJSHandlerTest, self).tearDown()
def test_time_fn(self):
"""Test self.time_fn."""
request = webapp2.Request.blank('/_/ts_mon_js')
response = webapp2.Response()
ts_mon_handler = handlers.TSMonJSHandler(request, response)
self.assertTrue(isinstance(ts_mon_handler.time_fn(), float))
def test_post_metrics_no_metrics(self):
"""Test case when class is not set up properly by calling register_metrics.
"""
request = webapp2.Request.blank('/_/ts_mon_js')
response = webapp2.Response()
ts_mon_handler = handlers.TSMonJSHandler(request=request, response=response)
ts_mon_handler.post()
self.assertEqual(response.status_int, 400)
self.assertIn('No metrics', response.body)
def test_post_metrics_invalid_json(self):
"""Test case when JSON request body is invalid."""
self.request.body = 'rutabaga'
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 400)
def test_post_metrics_invalid_xsrf(self):
"""Test case when XSRF token is invalid."""
self.request.body = '{}'
self.ts_mon_handler.xsrf_is_valid = mock.Mock(return_value=False)
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 403)
self.ts_mon_handler.xsrf_is_valid.assert_called_once_with({})
def test_post_metrics_must_be_dict(self):
"""Test case when body is not a dict."""
self.request.body = '[]'
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 400)
self.assertIn('dictionary', self.response.body)
def test_post_no_metrics_key(self):
"""Test case when dict does not contain 'metrics' key."""
self.request.body = '{}'
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 400)
self.assertIn('Key "metrics"', self.response.body)
def test_post_metrics_unregistered_metric_name(self):
"""Test case when a metric name isn't registered."""
self.request.body = json.dumps({
'metrics': [
{
'MetricInfo': {
'Name': 'frontend/not_defined',
'ValueType': 2,
},
'Cells': [{
'value': 'rutabaga',
'fields': {
'client_id': '789',
'rutabaga_id': '789',
},
}],
},
],
})
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 400)
self.assertIn('is not defined', self.response.body)
def test_post_metrics_malcious_metric_name(self):
"""Metric name is echoed back in a safely escaped form."""
self.request.body = json.dumps({
'metrics': [
{
'MetricInfo': {
'Name': 'frontend/not_defined<script>alert(1)</script>',
'ValueType': 2,
},
'Cells': [{
'value': 'rutabaga',
'fields': {
'client_id': '789',
'rutabaga_id': '789',
},
}],
},
],
})
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 400)
self.assertNotIn('<script>', self.response.body)
self.assertIn('is not defined', self.response.body)
def test_post_metrics_invalid_fields(self):
"""Test case when metric name is fine but fields are not."""
self.request.body = json.dumps({
'metrics': [
{
'MetricInfo': {
'Name': 'frontend/boolean_test',
'ValueType': 2,
},
'Cells': [{
'value': True,
'fields': {
'client_id': '789',
'rutabaga_id': '789',
},
}],
},
],
})
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 400)
self.assertIn('fields do not match', self.response.body)
def test_post_metrics_maliciou_fields(self):
"""Invalid fields are echoed in a safely escaped format."""
self.request.body = json.dumps({
'metrics': [
{
'MetricInfo': {
'Name': 'frontend/boolean_test',
'ValueType': 2,
},
'Cells': [{
'value': True,
'fields': {
'client_id<script>alert(1)</script>': '789',
'rutabaga_id': '789',
},
}],
},
],
})
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 400)
self.assertNotIn('<script>', self.response.body)
self.assertIn('fields do not match', self.response.body)
def test_post_rejects_cumulative_without_start_time(self):
"""Test case where start_time is not supplied for CumulativeDistribution."""
self.request.body = json.dumps({
'metrics': [
{
'MetricInfo': {
'Name': 'frontend/cumulative_test',
'ValueType': 2,
},
'Cells': [{
'value': 'rutabaga',
'fields': {
'client_id': '789',
},
}],
},
],
})
self.ts_mon_handler.register_metrics([
metrics.CumulativeDistributionMetric(
'frontend/cumulative_test', 'Cumulative metric test',
field_spec=[metrics.StringField('client_id')]),
])
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 400)
self.assertIn('Cumulative metrics', self.response.body)
def test_post_rejects_start_time_in_future(self):
"""Test rejects when start_time is in the future."""
self.request.body = json.dumps({
'metrics': [
{
'MetricInfo': {
'Name': 'frontend/cumulative_test',
'ValueType': 2,
},
'Cells': [{
'value': 'rutabaga',
'fields': {
'client_id': '789',
},
'start_time': self.mock_timestamp + 1,
}],
},
],
})
self.ts_mon_handler.register_metrics([
metrics.CumulativeDistributionMetric(
'frontend/cumulative_test', 'Cumulative metric test',
field_spec=[metrics.StringField('client_id')]),
])
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 400)
self.assertIn('Invalid start_time', self.response.body)
def test_post_rejects_start_time_in_past(self):
"""Test rejects when start_time is >1 month in the past."""
one_month_seconds = 60*60*24*30
self.request.body = json.dumps({
'metrics': [
{
'MetricInfo': {
'Name': 'frontend/cumulative_test',
'ValueType': 2,
},
'Cells': [{
'value': 'rutabaga',
'fields': {
'client_id': '789',
},
'start_time': self.mock_timestamp - one_month_seconds * 2,
}],
},
],
})
self.ts_mon_handler.register_metrics([
metrics.CumulativeDistributionMetric(
'frontend/cumulative_test', 'Cumulative metric test',
field_spec=[metrics.StringField('client_id')]),
])
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 400)
self.assertIn('Invalid start_time', self.response.body)
def test_post_distribution_metrics_not_a_dict(self):
"""Test case when a distribution metric value is not a dict."""
self.request.body = json.dumps({
'metrics': [
{
'MetricInfo': {
'Name': 'frontend/cumulative_test',
'ValueType': 2,
},
'Cells': [{
'value': 'rutabaga',
'fields': {
'client_id': '789',
},
'start_time': self.mock_timestamp - 60,
}],
},
],
})
self.ts_mon_handler.register_metrics([
metrics.CumulativeDistributionMetric(
'frontend/cumulative_test', 'Cumulative metric test',
field_spec=[metrics.StringField('client_id')]),
])
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 400)
self.assertIn('Distribution metric values must be a dict',
self.response.body)
def test_post_metrics_normal(self):
"""Test successful POST case."""
self.request.body = json.dumps({
'metrics': [
{
'MetricInfo': {
'Name': 'frontend/boolean_test',
'ValueType': 2,
},
'Cells': [{
'value': True,
'fields': {
'client_id': '789',
},
}],
}, {
'MetricInfo': {
'Name': 'frontend/cumulative_test',
'ValueType': 2,
},
'Cells': [{
'value': {
'sum': 1234,
'count': 4321,
'buckets': {
0: 123,
1: 321,
2: 213,
},
},
'fields': {
'client_id': '789',
},
'start_time': self.mock_timestamp - 60,
}],
},
],
})
self.ts_mon_handler.register_metrics([
metrics.CumulativeDistributionMetric(
'frontend/cumulative_test', 'Cumulative metric test',
field_spec=[metrics.StringField('client_id')]),
])
self.ts_mon_handler.post()
self.assertEqual(self.response.status_int, 201)
class ReportMemoryTest(test_case.TestCase):
def test_report_skipped_if_development(self):
env = os.environ.copy()
env['SERVER_SOFTWARE'] = 'Development'
with mock.patch.object(os, 'environ', env):
@handlers.report_memory
def handler(request):
return request
with mock.patch.object(runtime, 'memory_usage') as m:
handler('input params')
m.assert_not_called()
def test_report_with_production(self):
env = os.environ.copy()
env['SERVER_SOFTWARE'] = 'Production'
with mock.patch.object(os, 'environ', env):
# a test handler should be added in the scope of the mock for os.environ.
@handlers.report_memory
def handler(request):
return request
with mock.patch.object(runtime, 'memory_usage') as m:
handler('input params')
m.assert_called()