Skip to content

Commit 411323b

Browse files
committed
Merge pull request influxdata#179 from georgijd/f-more-operations
Add more operations to the client (Thanks @georgijd !)
2 parents ab2e66c + 731c4e0 commit 411323b

File tree

3 files changed

+335
-0
lines changed

3 files changed

+335
-0
lines changed

influxdb/client.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,43 @@ def create_retention_policy(self, name, duration, replication,
470470

471471
self.query(query_string)
472472

473+
def alter_retention_policy(self, name, database=None,
474+
duration=None, replication=None, default=None):
475+
"""Mofidy an existing retention policy for a database.
476+
477+
:param name: the name of the retention policy to modify
478+
:type name: str
479+
:param database: the database for which the retention policy is
480+
modified. Defaults to current client's database
481+
:type database: str
482+
:param duration: the new duration of the existing retention policy.
483+
Durations such as 1h, 90m, 12h, 7d, and 4w, are all supported
484+
and mean 1 hour, 90 minutes, 12 hours, 7 day, and 4 weeks,
485+
respectively. For infinite retention – meaning the data will
486+
never be deleted – use 'INF' for duration.
487+
The minimum retention period is 1 hour.
488+
:type duration: str
489+
:param replication: the new replication of the existing
490+
retention policy
491+
:type replication: str
492+
:param default: whether or not to set the modified policy as default
493+
:type default: bool
494+
495+
.. note:: at least one of duration, replication, or default flag
496+
should be set. Otherwise the operation will fail.
497+
"""
498+
query_string = (
499+
"ALTER RETENTION POLICY {} ON {}"
500+
).format(name, database or self._database)
501+
if duration:
502+
query_string += " DURATION {}".format(duration)
503+
if replication:
504+
query_string += " REPLICATION {}".format(replication)
505+
if default is True:
506+
query_string += " DEFAULT"
507+
508+
self.query(query_string)
509+
473510
def get_list_retention_policies(self, database=None):
474511
"""Get the list of retention policies for a database.
475512
@@ -581,6 +618,62 @@ def delete_series(self, name, database=None):
581618
database = database or self._database
582619
self.query('DROP SERIES \"%s\"' % name, database=database)
583620

621+
def grant_admin_privileges(self, username):
622+
"""Grant cluster administration privileges to an user.
623+
624+
:param username: the username to grant privileges to
625+
:type username: str
626+
627+
.. note:: Only a cluster administrator can create/ drop databases
628+
and manage users.
629+
"""
630+
text = "GRANT ALL PRIVILEGES TO {}".format(username)
631+
self.query(text)
632+
633+
def revoke_admin_privileges(self, username):
634+
"""Revoke cluster administration privileges from an user.
635+
636+
:param username: the username to revoke privileges from
637+
:type username: str
638+
639+
.. note:: Only a cluster administrator can create/ drop databases
640+
and manage users.
641+
"""
642+
text = "REVOKE ALL PRIVILEGES FROM {}".format(username)
643+
self.query(text)
644+
645+
def grant_privilege(self, privilege, database, username):
646+
"""Grant a privilege on a database to an user.
647+
648+
:param privilege: the privilege to grant, one of 'read', 'write'
649+
or 'all'. The string is case-insensitive
650+
:type privilege: str
651+
:param database: the database to grant the privilege on
652+
:type database: str
653+
:param username: the username to grant the privilege to
654+
:type username: str
655+
"""
656+
text = "GRANT {} ON {} TO {}".format(privilege,
657+
database,
658+
username)
659+
self.query(text)
660+
661+
def revoke_privilege(self, privilege, database, username):
662+
"""Revoke a privilege on a database from an user.
663+
664+
:param privilege: the privilege to revoke, one of 'read', 'write'
665+
or 'all'. The string is case-insensitive
666+
:type privilege: str
667+
:param database: the database to revoke the privilege on
668+
:type database: str
669+
:param username: the username to revoke the privilege from
670+
:type username: str
671+
"""
672+
text = "REVOKE {} ON {} FROM {}".format(privilege,
673+
database,
674+
username)
675+
self.query(text)
676+
584677
def send_packet(self, packet):
585678
"""Send an UDP packet.
586679

tests/influxdb/client_test.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,44 @@ def test_create_retention_policy(self):
490490
'db duration 1d replication 4'
491491
)
492492

493+
def test_alter_retention_policy(self):
494+
example_response = '{"results":[{}]}'
495+
496+
with requests_mock.Mocker() as m:
497+
m.register_uri(
498+
requests_mock.GET,
499+
"http://localhost:8086/query",
500+
text=example_response
501+
)
502+
# Test alter duration
503+
self.cli.alter_retention_policy('somename', 'db',
504+
duration='4d')
505+
self.assertEqual(
506+
m.last_request.qs['q'][0],
507+
'alter retention policy somename on db duration 4d'
508+
)
509+
# Test alter replication
510+
self.cli.alter_retention_policy('somename', 'db',
511+
replication=4)
512+
self.assertEqual(
513+
m.last_request.qs['q'][0],
514+
'alter retention policy somename on db replication 4'
515+
)
516+
517+
# Test alter default
518+
self.cli.alter_retention_policy('somename', 'db',
519+
default=True)
520+
self.assertEqual(
521+
m.last_request.qs['q'][0],
522+
'alter retention policy somename on db default'
523+
)
524+
525+
@raises(Exception)
526+
def test_alter_retention_policy_invalid(self):
527+
cli = InfluxDBClient('host', 8086, 'username', 'password')
528+
with _mocked_session(cli, 'get', 400):
529+
self.cli.alter_retention_policy('somename', 'db')
530+
493531
def test_get_list_retention_policies(self):
494532
example_response = \
495533
'{"results": [{"series": [{"values": [["fsfdsdf", "24h0m0s", 2]],'\
@@ -586,6 +624,94 @@ def test_get_list_users_empty(self):
586624

587625
self.assertListEqual(self.cli.get_list_users(), [])
588626

627+
def test_grant_admin_privileges(self):
628+
example_response = '{"results":[{}]}'
629+
630+
with requests_mock.Mocker() as m:
631+
m.register_uri(
632+
requests_mock.GET,
633+
"http://localhost:8086/query",
634+
text=example_response
635+
)
636+
self.cli.grant_admin_privileges('test')
637+
638+
self.assertEqual(
639+
m.last_request.qs['q'][0],
640+
'grant all privileges to test'
641+
)
642+
643+
@raises(Exception)
644+
def test_grant_admin_privileges_invalid(self):
645+
cli = InfluxDBClient('host', 8086, 'username', 'password')
646+
with _mocked_session(cli, 'get', 400):
647+
self.cli.grant_admin_privileges('')
648+
649+
def test_revoke_admin_privileges(self):
650+
example_response = '{"results":[{}]}'
651+
652+
with requests_mock.Mocker() as m:
653+
m.register_uri(
654+
requests_mock.GET,
655+
"http://localhost:8086/query",
656+
text=example_response
657+
)
658+
self.cli.revoke_admin_privileges('test')
659+
660+
self.assertEqual(
661+
m.last_request.qs['q'][0],
662+
'revoke all privileges from test'
663+
)
664+
665+
@raises(Exception)
666+
def test_revoke_admin_privileges_invalid(self):
667+
cli = InfluxDBClient('host', 8086, 'username', 'password')
668+
with _mocked_session(cli, 'get', 400):
669+
self.cli.revoke_admin_privileges('')
670+
671+
def test_grant_privilege(self):
672+
example_response = '{"results":[{}]}'
673+
674+
with requests_mock.Mocker() as m:
675+
m.register_uri(
676+
requests_mock.GET,
677+
"http://localhost:8086/query",
678+
text=example_response
679+
)
680+
self.cli.grant_privilege('read', 'testdb', 'test')
681+
682+
self.assertEqual(
683+
m.last_request.qs['q'][0],
684+
'grant read on testdb to test'
685+
)
686+
687+
@raises(Exception)
688+
def test_grant_privilege_invalid(self):
689+
cli = InfluxDBClient('host', 8086, 'username', 'password')
690+
with _mocked_session(cli, 'get', 400):
691+
self.cli.grant_privilege('', 'testdb', 'test')
692+
693+
def test_revoke_privilege(self):
694+
example_response = '{"results":[{}]}'
695+
696+
with requests_mock.Mocker() as m:
697+
m.register_uri(
698+
requests_mock.GET,
699+
"http://localhost:8086/query",
700+
text=example_response
701+
)
702+
self.cli.revoke_privilege('read', 'testdb', 'test')
703+
704+
self.assertEqual(
705+
m.last_request.qs['q'][0],
706+
'revoke read on testdb from test'
707+
)
708+
709+
@raises(Exception)
710+
def test_revoke_privilege_invalid(self):
711+
cli = InfluxDBClient('host', 8086, 'username', 'password')
712+
with _mocked_session(cli, 'get', 400):
713+
self.cli.revoke_privilege('', 'testdb', 'test')
714+
589715

590716
class FakeClient(InfluxDBClient):
591717
fail = False

tests/influxdb/client_test_with_server.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,67 @@ def test_drop_user_invalid(self):
376376
'found invalid, expected',
377377
ctx.exception.content)
378378

379+
def test_grant_admin_privileges(self):
380+
self.cli.create_user('test', 'test')
381+
self.assertEqual([{'user': 'test', 'admin': False}],
382+
self.cli.get_list_users())
383+
self.cli.grant_admin_privileges('test')
384+
self.assertEqual([{'user': 'test', 'admin': True}],
385+
self.cli.get_list_users())
386+
387+
def test_grant_admin_privileges_invalid(self):
388+
with self.assertRaises(InfluxDBClientError) as ctx:
389+
self.cli.grant_admin_privileges('')
390+
self.assertEqual(400, ctx.exception.code)
391+
self.assertIn('{"error":"error parsing query: ',
392+
ctx.exception.content)
393+
394+
def test_revoke_admin_privileges(self):
395+
self.cli.create_user('test', 'test')
396+
self.cli.grant_admin_privileges('test')
397+
self.assertEqual([{'user': 'test', 'admin': True}],
398+
self.cli.get_list_users())
399+
self.cli.revoke_admin_privileges('test')
400+
self.assertEqual([{'user': 'test', 'admin': False}],
401+
self.cli.get_list_users())
402+
403+
def test_revoke_admin_privileges_invalid(self):
404+
with self.assertRaises(InfluxDBClientError) as ctx:
405+
self.cli.revoke_admin_privileges('')
406+
self.assertEqual(400, ctx.exception.code)
407+
self.assertIn('{"error":"error parsing query: ',
408+
ctx.exception.content)
409+
410+
def test_grant_privilege(self):
411+
self.cli.create_user('test', 'test')
412+
self.cli.create_database('testdb')
413+
self.cli.grant_privilege('all', 'testdb', 'test')
414+
# TODO: when supported by InfluxDB, check if privileges are granted
415+
416+
def test_grant_privilege_invalid(self):
417+
self.cli.create_user('test', 'test')
418+
self.cli.create_database('testdb')
419+
with self.assertRaises(InfluxDBClientError) as ctx:
420+
self.cli.grant_privilege('', 'testdb', 'test')
421+
self.assertEqual(400, ctx.exception.code)
422+
self.assertIn('{"error":"error parsing query: ',
423+
ctx.exception.content)
424+
425+
def test_revoke_privilege(self):
426+
self.cli.create_user('test', 'test')
427+
self.cli.create_database('testdb')
428+
self.cli.revoke_privilege('all', 'testdb', 'test')
429+
# TODO: when supported by InfluxDB, check if privileges are revoked
430+
431+
def test_revoke_privilege_invalid(self):
432+
self.cli.create_user('test', 'test')
433+
self.cli.create_database('testdb')
434+
with self.assertRaises(InfluxDBClientError) as ctx:
435+
self.cli.revoke_privilege('', 'testdb', 'test')
436+
self.assertEqual(400, ctx.exception.code)
437+
self.assertIn('{"error":"error parsing query: ',
438+
ctx.exception.content)
439+
379440

380441
############################################################################
381442

@@ -657,6 +718,61 @@ def test_create_retention_policy(self):
657718
rsp
658719
)
659720

721+
def test_alter_retention_policy(self):
722+
self.cli.create_retention_policy('somename', '1d', 1)
723+
724+
# Test alter duration
725+
self.cli.alter_retention_policy('somename', 'db',
726+
duration='4d')
727+
rsp = self.cli.get_list_retention_policies()
728+
self.assertEqual(
729+
[{'duration': '0', 'default': True,
730+
'replicaN': 1, 'name': 'default'},
731+
{'duration': '96h0m0s', 'default': False,
732+
'replicaN': 1, 'name': 'somename'}],
733+
rsp
734+
)
735+
736+
# Test alter replication
737+
self.cli.alter_retention_policy('somename', 'db',
738+
replication=4)
739+
rsp = self.cli.get_list_retention_policies()
740+
self.assertEqual(
741+
[{'duration': '0', 'default': True,
742+
'replicaN': 1, 'name': 'default'},
743+
{'duration': '96h0m0s', 'default': False,
744+
'replicaN': 4, 'name': 'somename'}],
745+
rsp
746+
)
747+
748+
# Test alter default
749+
self.cli.alter_retention_policy('somename', 'db',
750+
default=True)
751+
rsp = self.cli.get_list_retention_policies()
752+
self.assertEqual(
753+
[{'duration': '0', 'default': False,
754+
'replicaN': 1, 'name': 'default'},
755+
{'duration': '96h0m0s', 'default': True,
756+
'replicaN': 4, 'name': 'somename'}],
757+
rsp
758+
)
759+
760+
def test_alter_retention_policy_invalid(self):
761+
self.cli.create_retention_policy('somename', '1d', 1)
762+
with self.assertRaises(InfluxDBClientError) as ctx:
763+
self.cli.alter_retention_policy('somename', 'db')
764+
self.assertEqual(400, ctx.exception.code)
765+
self.assertIn('{"error":"error parsing query: ',
766+
ctx.exception.content)
767+
rsp = self.cli.get_list_retention_policies()
768+
self.assertEqual(
769+
[{'duration': '0', 'default': True,
770+
'replicaN': 1, 'name': 'default'},
771+
{'duration': '24h0m0s', 'default': False,
772+
'replicaN': 1, 'name': 'somename'}],
773+
rsp
774+
)
775+
660776
def test_issue_143(self):
661777
pt = partial(point, 'a_serie_name', timestamp='2015-03-30T16:16:37Z')
662778
pts = [

0 commit comments

Comments
 (0)