Skip to content

Commit 15b58dd

Browse files
author
Frederick Ross
committed
Made TCP inputs handle restrictToHost.
1 parent 1ca24bf commit 15b58dd

File tree

2 files changed

+116
-16
lines changed

2 files changed

+116
-16
lines changed

splunklib/client.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,9 @@ def _proper_namespace(self, owner=None, app=None, sharing=None):
797797
sharing = self._state.access.sharing
798798
return (owner, app, sharing)
799799

800+
def delete(self):
801+
owner, app, sharing = self._proper_namespace()
802+
return self.service.delete(self.path, owner=owner, app=app, sharing=sharing)
800803

801804
def get(self, path_segment="", owner=None, app=None, sharing=None, **query):
802805
owner, app, sharing = self._proper_namespace(owner, app, sharing)
@@ -1679,6 +1682,21 @@ def __init__(self, service, path, kind=None, **kwargs):
16791682
if self.kind == 'tcp/cooked':
16801683
self.kind = 'splunktcp'
16811684

1685+
def update(self, **kwargs):
1686+
if 'restrictToHost' in kwargs and self.service.splunk_version < (5,):
1687+
raise IllegalOperationException("Updating restrictToHost has no effect before Splunk 5.0")
1688+
port = self.name.split(':', 1)[-1]
1689+
result = super(Input, self).update(**kwargs)
1690+
if 'restrictToHost' in kwargs:
1691+
if self.path.endswith('/'):
1692+
base_path, name = self.path.rsplit('/', 2)[:-1]
1693+
else:
1694+
base_path, name = self.path.rsplit('/', 1)
1695+
self.path = base_path + '/' + kwargs['restrictToHost'] + ':' + port
1696+
return result
1697+
1698+
1699+
16821700
# Inputs is a "kinded" collection, which is a heterogenous collection where
16831701
# each item is tagged with a kind, that provides a single merged view of all
16841702
# input kinds.
@@ -1776,8 +1794,15 @@ def create(self, kind, name, **kwargs):
17761794
:return: The new input.
17771795
"""
17781796
kindpath = self.kindpath(kind)
1779-
self.post(kindpath, name=name, **kwargs)
1780-
path = _path(self.path + kindpath, name)
1797+
if (kindpath == 'tcp/raw' or kindpath == 'tcp/cooked' or kindpath == 'udp') and \
1798+
'restrictToHost' in kwargs:
1799+
post_name = name
1800+
path_name = kwargs['restrictToHost'] + ':' + name
1801+
else:
1802+
post_name = name
1803+
path_name = name
1804+
self.post(kindpath, name=post_name, **kwargs)
1805+
path = _path(self.path + kindpath, path_name)
17811806
return Input(self.service, path, kind)
17821807

17831808
def delete(self, kind, name=None):
@@ -1864,7 +1889,7 @@ def list(self, *kinds, **kwargs):
18641889
path = self.kindpath(kind)
18651890
logging.debug("Path for inputs: %s", path)
18661891
try:
1867-
response = self.service.get(path, **kwargs)
1892+
response = self.get(path, **kwargs)
18681893
except HTTPError, he:
18691894
if he.status == 404: # No inputs of this kind
18701895
return []

tests/test_input.py

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,85 @@
1919

2020
import splunklib.client as client
2121

22+
def highest_port(service, base_port, *kinds):
23+
"""Find the first port >= base_port not in use by any input in kinds."""
24+
highest_port = base_port
25+
for input in service.inputs.list(*kinds):
26+
port = int(input.name.split(':')[-1])
27+
highest_port = max(port, highest_port)
28+
return highest_port
29+
30+
class TestTcpInputNameHandling(testlib.SDKTestCase):
31+
def setUp(self):
32+
super(TestTcpInputNameHandling, self).setUp()
33+
self.base_port = highest_port(self.service, 10000, 'tcp', 'splunktcp') + 1
34+
35+
def tearDown(self):
36+
for input in self.service.inputs.list('tcp', 'splunktcp'):
37+
port = int(input.name.split(':')[-1])
38+
if port >= self.base_port:
39+
input.delete()
40+
super(TestTcpInputNameHandling, self).tearDown()
41+
42+
def test_create_tcp_port(self):
43+
for kind in ['tcp', 'splunktcp']:
44+
input = self.service.inputs.create(kind, str(self.base_port))
45+
self.check_entity(input)
46+
input.delete()
47+
48+
def test_cannot_create_with_restrictToHost_in_name(self):
49+
self.assertRaises(
50+
client.HTTPError,
51+
lambda: self.service.inputs.create('tcp', 'boris:10000')
52+
)
53+
54+
def test_create_tcp_ports_with_restrictToHost(self):
55+
for kind in ['tcp', 'splunktcp']:
56+
# Make sure we can create two restricted inputs on the same port
57+
boris = self.service.inputs.create(kind, str(self.base_port), restrictToHost='boris')
58+
natasha = self.service.inputs.create(kind, str(self.base_port), restrictToHost='natasha')
59+
# And that they both function
60+
boris.refresh()
61+
natasha.refresh()
62+
self.check_entity(boris)
63+
self.check_entity(natasha)
64+
boris.delete()
65+
natasha.delete()
66+
67+
def test_restricted_to_unrestricted_collision(self):
68+
for kind in ['tcp', 'splunktcp']:
69+
restricted = self.service.inputs.create(kind, str(self.base_port), restrictToHost='boris')
70+
self.assertRaises(
71+
client.HTTPError,
72+
lambda: self.service.inputs.create(kind, str(self.base_port))
73+
)
74+
restricted.delete()
75+
76+
def test_unrestricted_to_restricted_collision(self):
77+
for kind in ['tcp', 'splunktcp']:
78+
unrestricted = self.service.inputs.create(kind, str(self.base_port))
79+
self.assertRaises(
80+
client.HTTPError,
81+
lambda: self.service.inputs.create(kind, str(self.base_port), restrictToHos='boris')
82+
)
83+
unrestricted.delete()
84+
85+
def test_update_restrictToHost(self):
86+
for kind in ['tcp', 'splunktcp']:
87+
boris = self.service.inputs.create(kind, str(self.base_port), restrictToHost='boris')
88+
with self.fake_splunk_version((4,3)):
89+
self.assertRaises(
90+
client.IllegalOperationException,
91+
lambda: boris.update(restrictToHost='hilda')
92+
)
93+
if self.service.splunk_version >= (5,):
94+
boris.update(restrictToHost='hilda')
95+
boris.refresh()
96+
self.assertEqual('hilda:' + str(self.base_port), boris.name)
97+
boris.refresh()
98+
self.check_entity(boris)
99+
boris.delete()
100+
22101
class TestRead(testlib.SDKTestCase):
23102
def test_read(self):
24103
inputs = self.service.inputs
@@ -80,26 +159,20 @@ def test_oneshot_on_nonexistant_file(self):
80159
self.assertRaises(client.OperationFailedException,
81160
self.service.inputs.oneshot, name)
82161

83-
84162
class TestInput(testlib.SDKTestCase):
85163
def setUp(self):
86164
super(TestInput, self).setUp()
87165
inputs = self.service.inputs
88-
test_inputs = [{'kind': 'tcp', 'name': '9999', 'host': 'sdk-test'},
89-
{'kind': 'udp', 'name': '9999', 'host': 'sdk-test'}]
166+
tcp_port = str(highest_port(self.service, 10000, 'tcp', 'splunktcp')+1)
167+
udp_port = str(highest_port(self.service, 10000, 'udp')+1)
168+
test_inputs = [{'kind': 'tcp', 'name': tcp_port, 'host': 'sdk-test'},
169+
{'kind': 'udp', 'name': udp_port, 'host': 'sdk-test'}]
90170
self._test_entities = {}
91171

92-
base_port = 10000
93-
while True:
94-
if str(base_port) in inputs:
95-
base_port += 1
96-
else:
97-
break
98-
99172
self._test_entities['tcp'] = \
100-
inputs.create('tcp', str(base_port), host='sdk-test')
173+
inputs.create('tcp', str(tcp_port), host='sdk-test')
101174
self._test_entities['udp'] = \
102-
inputs.create('udp', str(base_port), host='sdk-test')
175+
inputs.create('udp', str(udp_port), host='sdk-test')
103176

104177
def tearDown(self):
105178
super(TestInput, self).tearDown()
@@ -184,7 +257,9 @@ def test_delete(self):
184257
self.assertRaises(client.EntityDeletedException,
185258
input_entity.refresh)
186259
remaining -= 1
187-
260+
261+
262+
188263

189264
if __name__ == "__main__":
190265
import unittest

0 commit comments

Comments
 (0)