# -*- coding: utf-8 -*- """Client unit tests.""" import json import socket import sys import unittest import random import warnings import mock import requests import requests.exceptions import requests_mock from nose.tools import raises from mock import patch from influxdb.influxdb08 import InfluxDBClient from influxdb.influxdb08.client import session if sys.version < '3': import codecs def u(x): """Test codec.""" return codecs.unicode_escape_decode(x)[0] else: def u(x): """Test codec.""" return x def _build_response_object(status_code=200, content=""): resp = requests.Response() resp.status_code = status_code resp._content = content.encode("utf8") return resp def _mocked_session(method="GET", status_code=200, content=""): method = method.upper() def request(*args, **kwargs): """Define a request for the _mocked_session.""" c = content # Check method assert method == kwargs.get('method', 'GET') if method == 'POST': data = kwargs.get('data', None) if data is not None: # Data must be a string assert isinstance(data, str) # Data must be a JSON string assert c == json.loads(data, strict=True) c = data # Anyway, Content must be a JSON string (or empty string) if not isinstance(c, str): c = json.dumps(c) return _build_response_object(status_code=status_code, content=c) mocked = patch.object( session, 'request', side_effect=request ) return mocked class TestInfluxDBClient(unittest.TestCase): """Define a TestInfluxDBClient object.""" def setUp(self): """Set up a TestInfluxDBClient object.""" # By default, raise exceptions on warnings warnings.simplefilter('error', FutureWarning) self.dummy_points = [ { "points": [ ["1", 1, 1.0], ["2", 2, 2.0] ], "name": "foo", "columns": ["column_one", "column_two", "column_three"] } ] self.dsn_string = 'influxdb://uSr:pWd@host:1886/db' def test_scheme(self): """Test database scheme for TestInfluxDBClient object.""" cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') self.assertEqual(cli._baseurl, 'http://host:8086') cli = InfluxDBClient( 'host', 8086, 'username', 'password', 'database', ssl=True ) self.assertEqual(cli._baseurl, 'https://host:8086') def test_dsn(self): """Test datasource name for TestInfluxDBClient object.""" cli = InfluxDBClient.from_dsn(self.dsn_string) self.assertEqual('http://host:1886', cli._baseurl) self.assertEqual('uSr', cli._username) self.assertEqual('pWd', cli._password) self.assertEqual('db', cli._database) self.assertFalse(cli._use_udp) cli = InfluxDBClient.from_dsn('udp+' + self.dsn_string) self.assertTrue(cli._use_udp) cli = InfluxDBClient.from_dsn('https+' + self.dsn_string) self.assertEqual('https://host:1886', cli._baseurl) cli = InfluxDBClient.from_dsn('https+' + self.dsn_string, **{'ssl': False}) self.assertEqual('http://host:1886', cli._baseurl) def test_switch_database(self): """Test switch database for TestInfluxDBClient object.""" cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') cli.switch_database('another_database') self.assertEqual(cli._database, 'another_database') @raises(FutureWarning) def test_switch_db_deprecated(self): """Test deprecated switch database for TestInfluxDBClient object.""" cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') cli.switch_db('another_database') self.assertEqual(cli._database, 'another_database') def test_switch_user(self): """Test switch user for TestInfluxDBClient object.""" cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') cli.switch_user('another_username', 'another_password') self.assertEqual(cli._username, 'another_username') self.assertEqual(cli._password, 'another_password') def test_write(self): """Test write to database for TestInfluxDBClient object.""" with requests_mock.Mocker() as m: m.register_uri( requests_mock.POST, "http://localhost:8086/write" ) cli = InfluxDBClient(database='db') cli.write( {"database": "mydb", "retentionPolicy": "mypolicy", "points": [{"name": "cpu_load_short", "tags": {"host": "server01", "region": "us-west"}, "timestamp": "2009-11-10T23:00:00Z", "values": {"value": 0.64}}]} ) self.assertEqual( json.loads(m.last_request.body), {"database": "mydb", "retentionPolicy": "mypolicy", "points": [{"name": "cpu_load_short", "tags": {"host": "server01", "region": "us-west"}, "timestamp": "2009-11-10T23:00:00Z", "values": {"value": 0.64}}]} ) def test_write_points(self): """Test write points for TestInfluxDBClient object.""" with requests_mock.Mocker() as m: m.register_uri( requests_mock.POST, "http://localhost:8086/db/db/series" ) cli = InfluxDBClient(database='db') cli.write_points( self.dummy_points ) self.assertListEqual( json.loads(m.last_request.body), self.dummy_points ) def test_write_points_string(self): """Test write string points for TestInfluxDBClient object.""" with requests_mock.Mocker() as m: m.register_uri( requests_mock.POST, "http://localhost:8086/db/db/series" ) cli = InfluxDBClient(database='db') cli.write_points( str(json.dumps(self.dummy_points)) ) self.assertListEqual( json.loads(m.last_request.body), self.dummy_points ) def test_write_points_batch(self): """Test write batch points for TestInfluxDBClient object.""" with requests_mock.Mocker() as m: m.register_uri(requests_mock.POST, "http://localhost:8086/db/db/series") cli = InfluxDBClient('localhost', 8086, 'username', 'password', 'db') cli.write_points(data=self.dummy_points, batch_size=2) self.assertEqual(1, m.call_count) def test_write_points_batch_invalid_size(self): """Test write batch points invalid size for TestInfluxDBClient.""" with requests_mock.Mocker() as m: m.register_uri(requests_mock.POST, "http://localhost:8086/db/db/series") cli = InfluxDBClient('localhost', 8086, 'username', 'password', 'db') cli.write_points(data=self.dummy_points, batch_size=-2) self.assertEqual(1, m.call_count) def test_write_points_batch_multiple_series(self): """Test write points batch multiple series.""" dummy_points = [ {"points": [["1", 1, 1.0], ["2", 2, 2.0], ["3", 3, 3.0], ["4", 4, 4.0], ["5", 5, 5.0]], "name": "foo", "columns": ["val1", "val2", "val3"]}, {"points": [["1", 1, 1.0], ["2", 2, 2.0], ["3", 3, 3.0], ["4", 4, 4.0], ["5", 5, 5.0], ["6", 6, 6.0], ["7", 7, 7.0], ["8", 8, 8.0]], "name": "bar", "columns": ["val1", "val2", "val3"]}, ] expected_last_body = [{'points': [['7', 7, 7.0], ['8', 8, 8.0]], 'name': 'bar', 'columns': ['val1', 'val2', 'val3']}] with requests_mock.Mocker() as m: m.register_uri(requests_mock.POST, "http://localhost:8086/db/db/series") cli = InfluxDBClient('localhost', 8086, 'username', 'password', 'db') cli.write_points(data=dummy_points, batch_size=3) self.assertEqual(m.call_count, 5) self.assertEqual(expected_last_body, m.request_history[4].json()) def test_write_points_udp(self): """Test write points UDP for TestInfluxDBClient object.""" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) port = random.randint(4000, 8000) s.bind(('0.0.0.0', port)) cli = InfluxDBClient( 'localhost', 8086, 'root', 'root', 'test', use_udp=True, udp_port=port ) cli.write_points(self.dummy_points) received_data, addr = s.recvfrom(1024) self.assertEqual(self.dummy_points, json.loads(received_data.decode(), strict=True)) def test_write_bad_precision_udp(self): """Test write UDP w/bad precision.""" cli = InfluxDBClient( 'localhost', 8086, 'root', 'root', 'test', use_udp=True, udp_port=4444 ) with self.assertRaisesRegexp( Exception, "InfluxDB only supports seconds precision for udp writes" ): cli.write_points( self.dummy_points, time_precision='ms' ) @raises(Exception) def test_write_points_fails(self): """Test failed write points for TestInfluxDBClient object.""" with _mocked_session('post', 500): cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.write_points([]) def test_write_points_with_precision(self): """Test write points with precision.""" with _mocked_session('post', 200, self.dummy_points): cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') self.assertTrue(cli.write_points(self.dummy_points)) def test_write_points_bad_precision(self): """Test write points with bad precision.""" cli = InfluxDBClient() with self.assertRaisesRegexp( Exception, "Invalid time precision is given. \(use 's', 'm', 'ms' or 'u'\)" ): cli.write_points( self.dummy_points, time_precision='g' ) @raises(Exception) def test_write_points_with_precision_fails(self): """Test write points where precision fails.""" with _mocked_session('post', 500): cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.write_points_with_precision([]) def test_delete_points(self): """Test delete points for TestInfluxDBClient object.""" with _mocked_session('delete', 204) as mocked: cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') self.assertTrue(cli.delete_points("foo")) self.assertEqual(len(mocked.call_args_list), 1) args, kwds = mocked.call_args_list[0] self.assertEqual(kwds['params'], {'u': 'username', 'p': 'password'}) self.assertEqual(kwds['url'], 'http://host:8086/db/db/series/foo') @raises(Exception) def test_delete_points_with_wrong_name(self): """Test delete points with wrong name.""" with _mocked_session('delete', 400): cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.delete_points("nonexist") @raises(NotImplementedError) def test_create_scheduled_delete(self): """Test create scheduled deletes.""" cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.create_scheduled_delete([]) @raises(NotImplementedError) def test_get_list_scheduled_delete(self): """Test get schedule list of deletes TestInfluxDBClient.""" cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.get_list_scheduled_delete() @raises(NotImplementedError) def test_remove_scheduled_delete(self): """Test remove scheduled delete TestInfluxDBClient.""" cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.remove_scheduled_delete(1) def test_query(self): """Test query for TestInfluxDBClient object.""" data = [ { "name": "foo", "columns": ["time", "sequence_number", "column_one"], "points": [ [1383876043, 16, "2"], [1383876043, 15, "1"], [1383876035, 14, "2"], [1383876035, 13, "1"] ] } ] with _mocked_session('get', 200, data): cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') result = cli.query('select column_one from foo;') self.assertEqual(len(result[0]['points']), 4) def test_query_chunked(self): """Test chunked query for TestInfluxDBClient object.""" cli = InfluxDBClient(database='db') example_object = { 'points': [ [1415206250119, 40001, 667], [1415206244555, 30001, 7], [1415206228241, 20001, 788], [1415206212980, 10001, 555], [1415197271586, 10001, 23] ], 'name': 'foo', 'columns': [ 'time', 'sequence_number', 'val' ] } example_response = \ json.dumps(example_object) + json.dumps(example_object) with requests_mock.Mocker() as m: m.register_uri( requests_mock.GET, "http://localhost:8086/db/db/series", text=example_response ) self.assertListEqual( cli.query('select * from foo', chunked=True), [example_object, example_object] ) def test_query_chunked_unicode(self): """Test unicode chunked query for TestInfluxDBClient object.""" cli = InfluxDBClient(database='db') example_object = { 'points': [ [1415206212980, 10001, u('unicode-\xcf\x89')], [1415197271586, 10001, u('more-unicode-\xcf\x90')] ], 'name': 'foo', 'columns': [ 'time', 'sequence_number', 'val' ] } example_response = \ json.dumps(example_object) + json.dumps(example_object) with requests_mock.Mocker() as m: m.register_uri( requests_mock.GET, "http://localhost:8086/db/db/series", text=example_response ) self.assertListEqual( cli.query('select * from foo', chunked=True), [example_object, example_object] ) @raises(Exception) def test_query_fail(self): """Test failed query for TestInfluxDBClient.""" with _mocked_session('get', 401): cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.query('select column_one from foo;') def test_query_bad_precision(self): """Test query with bad precision for TestInfluxDBClient.""" cli = InfluxDBClient() with self.assertRaisesRegexp( Exception, "Invalid time precision is given. \(use 's', 'm', 'ms' or 'u'\)" ): cli.query('select column_one from foo', time_precision='g') def test_create_database(self): """Test create database for TestInfluxDBClient.""" with _mocked_session('post', 201, {"name": "new_db"}): cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') self.assertTrue(cli.create_database('new_db')) @raises(Exception) def test_create_database_fails(self): """Test failed create database for TestInfluxDBClient.""" with _mocked_session('post', 401): cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.create_database('new_db') def test_delete_database(self): """Test delete database for TestInfluxDBClient.""" with _mocked_session('delete', 204): cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') self.assertTrue(cli.delete_database('old_db')) @raises(Exception) def test_delete_database_fails(self): """Test failed delete database for TestInfluxDBClient.""" with _mocked_session('delete', 401): cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.delete_database('old_db') def test_get_list_database(self): """Test get list of databases for TestInfluxDBClient.""" data = [ {"name": "a_db"} ] with _mocked_session('get', 200, data): cli = InfluxDBClient('host', 8086, 'username', 'password') self.assertEqual(len(cli.get_list_database()), 1) self.assertEqual(cli.get_list_database()[0]['name'], 'a_db') @raises(Exception) def test_get_list_database_fails(self): """Test failed get list of databases for TestInfluxDBClient.""" with _mocked_session('get', 401): cli = InfluxDBClient('host', 8086, 'username', 'password') cli.get_list_database() @raises(FutureWarning) def test_get_database_list_deprecated(self): """Test deprecated get database list for TestInfluxDBClient.""" data = [ {"name": "a_db"} ] with _mocked_session('get', 200, data): cli = InfluxDBClient('host', 8086, 'username', 'password') self.assertEqual(len(cli.get_database_list()), 1) self.assertEqual(cli.get_database_list()[0]['name'], 'a_db') def test_delete_series(self): """Test delete series for TestInfluxDBClient.""" with _mocked_session('delete', 204): cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.delete_series('old_series') @raises(Exception) def test_delete_series_fails(self): """Test failed delete series for TestInfluxDBClient.""" with _mocked_session('delete', 401): cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.delete_series('old_series') def test_get_series_list(self): """Test get list of series for TestInfluxDBClient.""" cli = InfluxDBClient(database='db') with requests_mock.Mocker() as m: example_response = \ '[{"name":"list_series_result","columns":' \ '["time","name"],"points":[[0,"foo"],[0,"bar"]]}]' m.register_uri( requests_mock.GET, "http://localhost:8086/db/db/series", text=example_response ) self.assertListEqual( cli.get_list_series(), ['foo', 'bar'] ) def test_get_continuous_queries(self): """Test get continuous queries for TestInfluxDBClient.""" cli = InfluxDBClient(database='db') with requests_mock.Mocker() as m: # Tip: put this in a json linter! example_response = '[ { "name": "continuous queries", "columns"' \ ': [ "time", "id", "query" ], "points": [ [ ' \ '0, 1, "select foo(bar,95) from \\"foo_bar' \ 's\\" group by time(5m) into response_times.' \ 'percentiles.5m.95" ], [ 0, 2, "select perce' \ 'ntile(value,95) from \\"response_times\\" g' \ 'roup by time(5m) into response_times.percen' \ 'tiles.5m.95" ] ] } ]' m.register_uri( requests_mock.GET, "http://localhost:8086/db/db/series", text=example_response ) self.assertListEqual( cli.get_list_continuous_queries(), [ 'select foo(bar,95) from "foo_bars" group ' 'by time(5m) into response_times.percentiles.5m.95', 'select percentile(value,95) from "response_times" group ' 'by time(5m) into response_times.percentiles.5m.95' ] ) def test_get_list_cluster_admins(self): """Test get list of cluster admins, not implemented.""" pass def test_add_cluster_admin(self): """Test add cluster admin for TestInfluxDBClient.""" with requests_mock.Mocker() as m: m.register_uri( requests_mock.POST, "http://localhost:8086/cluster_admins" ) cli = InfluxDBClient(database='db') cli.add_cluster_admin( new_username='paul', new_password='laup' ) self.assertDictEqual( json.loads(m.last_request.body), { 'name': 'paul', 'password': 'laup' } ) def test_update_cluster_admin_password(self): """Test update cluster admin pass for TestInfluxDBClient.""" with requests_mock.Mocker() as m: m.register_uri( requests_mock.POST, "http://localhost:8086/cluster_admins/paul" ) cli = InfluxDBClient(database='db') cli.update_cluster_admin_password( username='paul', new_password='laup' ) self.assertDictEqual( json.loads(m.last_request.body), {'password': 'laup'} ) def test_delete_cluster_admin(self): """Test delete cluster admin for TestInfluxDBClient.""" with requests_mock.Mocker() as m: m.register_uri( requests_mock.DELETE, "http://localhost:8086/cluster_admins/paul", status_code=200, ) cli = InfluxDBClient(database='db') cli.delete_cluster_admin(username='paul') self.assertIsNone(m.last_request.body) def test_set_database_admin(self): """Test set database admin for TestInfluxDBClient.""" pass def test_unset_database_admin(self): """Test unset database admin for TestInfluxDBClient.""" pass def test_alter_database_admin(self): """Test alter database admin for TestInfluxDBClient.""" with requests_mock.Mocker() as m: m.register_uri( requests_mock.POST, "http://localhost:8086/db/db/users/paul" ) cli = InfluxDBClient(database='db') cli.alter_database_admin( username='paul', is_admin=False ) self.assertDictEqual( json.loads(m.last_request.body), { 'admin': False } ) @raises(NotImplementedError) def test_get_list_database_admins(self): """Test get list of database admins for TestInfluxDBClient.""" cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.get_list_database_admins() @raises(NotImplementedError) def test_add_database_admin(self): """Test add database admins for TestInfluxDBClient.""" cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.add_database_admin('admin', 'admin_secret_password') @raises(NotImplementedError) def test_update_database_admin_password(self): """Test update database admin pass for TestInfluxDBClient.""" cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.update_database_admin_password('admin', 'admin_secret_password') @raises(NotImplementedError) def test_delete_database_admin(self): """Test delete database admin for TestInfluxDBClient.""" cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.delete_database_admin('admin') def test_get_database_users(self): """Test get database users for TestInfluxDBClient.""" cli = InfluxDBClient('localhost', 8086, 'username', 'password', 'db') example_response = \ '[{"name":"paul","isAdmin":false,"writeTo":".*","readFrom":".*"},'\ '{"name":"bobby","isAdmin":false,"writeTo":".*","readFrom":".*"}]' with requests_mock.Mocker() as m: m.register_uri( requests_mock.GET, "http://localhost:8086/db/db/users", text=example_response ) users = cli.get_database_users() self.assertEqual(json.loads(example_response), users) def test_add_database_user(self): """Test add database user for TestInfluxDBClient.""" with requests_mock.Mocker() as m: m.register_uri( requests_mock.POST, "http://localhost:8086/db/db/users" ) cli = InfluxDBClient(database='db') cli.add_database_user( new_username='paul', new_password='laup', permissions=('.*', '.*') ) self.assertDictEqual( json.loads(m.last_request.body), { 'writeTo': '.*', 'password': 'laup', 'readFrom': '.*', 'name': 'paul' } ) def test_add_database_user_bad_permissions(self): """Test add database user with bad perms for TestInfluxDBClient.""" cli = InfluxDBClient() with self.assertRaisesRegexp( Exception, "'permissions' must be \(readFrom, writeTo\) tuple" ): cli.add_database_user( new_password='paul', new_username='paul', permissions=('hello', 'hello', 'hello') ) def test_alter_database_user_password(self): """Test alter database user pass for TestInfluxDBClient.""" with requests_mock.Mocker() as m: m.register_uri( requests_mock.POST, "http://localhost:8086/db/db/users/paul" ) cli = InfluxDBClient(database='db') cli.alter_database_user( username='paul', password='n3wp4ss!' ) self.assertDictEqual( json.loads(m.last_request.body), { 'password': 'n3wp4ss!' } ) def test_alter_database_user_permissions(self): """Test alter database user perms for TestInfluxDBClient.""" with requests_mock.Mocker() as m: m.register_uri( requests_mock.POST, "http://localhost:8086/db/db/users/paul" ) cli = InfluxDBClient(database='db') cli.alter_database_user( username='paul', permissions=('^$', '.*') ) self.assertDictEqual( json.loads(m.last_request.body), { 'readFrom': '^$', 'writeTo': '.*' } ) def test_alter_database_user_password_and_permissions(self): """Test alter database user pass and perms for TestInfluxDBClient.""" with requests_mock.Mocker() as m: m.register_uri( requests_mock.POST, "http://localhost:8086/db/db/users/paul" ) cli = InfluxDBClient(database='db') cli.alter_database_user( username='paul', password='n3wp4ss!', permissions=('^$', '.*') ) self.assertDictEqual( json.loads(m.last_request.body), { 'password': 'n3wp4ss!', 'readFrom': '^$', 'writeTo': '.*' } ) def test_update_database_user_password_current_user(self): """Test update database user pass for TestInfluxDBClient.""" cli = InfluxDBClient( username='root', password='hello', database='database' ) with requests_mock.Mocker() as m: m.register_uri( requests_mock.POST, "http://localhost:8086/db/database/users/root" ) cli.update_database_user_password( username='root', new_password='bye' ) self.assertEqual(cli._password, 'bye') def test_delete_database_user(self): """Test delete database user for TestInfluxDBClient.""" with requests_mock.Mocker() as m: m.register_uri( requests_mock.DELETE, "http://localhost:8086/db/db/users/paul" ) cli = InfluxDBClient(database='db') cli.delete_database_user(username='paul') self.assertIsNone(m.last_request.body) @raises(NotImplementedError) def test_update_permission(self): """Test update permission for TestInfluxDBClient.""" cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') cli.update_permission('admin', []) @mock.patch('requests.Session.request') def test_request_retry(self, mock_request): """Test that two connection errors will be handled.""" class CustomMock(object): """Define CustomMock object.""" def __init__(self): self.i = 0 def connection_error(self, *args, **kwargs): """Test connection error in CustomMock.""" self.i += 1 if self.i < 3: raise requests.exceptions.ConnectionError else: r = requests.Response() r.status_code = 200 return r mock_request.side_effect = CustomMock().connection_error cli = InfluxDBClient(database='db') cli.write_points( self.dummy_points ) @mock.patch('requests.Session.request') def test_request_retry_raises(self, mock_request): """Test that three connection errors will not be handled.""" class CustomMock(object): """Define CustomMock object.""" def __init__(self): """Initialize the object.""" self.i = 0 def connection_error(self, *args, **kwargs): """Test the connection error for CustomMock.""" self.i += 1 if self.i < 4: raise requests.exceptions.ConnectionError else: r = requests.Response() r.status_code = 200 return r mock_request.side_effect = CustomMock().connection_error cli = InfluxDBClient(database='db') with self.assertRaises(requests.exceptions.ConnectionError): cli.write_points(self.dummy_points)