@@ -517,25 +517,22 @@ def restart(self, timeout=None):
517517 :param timeout: A timeout period, in seconds.
518518 :type timeout: ``integer``
519519 """
520+ msg = { "value" : "Restart requested by " + self .username + "via the Splunk SDK for Python" }
521+ # This message will be deleted once the server actually restarts.
522+ self .messages .create (name = "restart_required" , ** msg )
520523 result = self .post ("server/control/restart" )
521- if timeout is None : return result
522- start = datetime .now ()
523- diff = timedelta (seconds = 10 )
524- while datetime .now () - start < diff :
525- try :
526- self .login () # Has the server gone down yet?
527- sleep (0.3 )
528- except Exception :
529- break # Server is down. Move on.
524+ if timeout is None :
525+ return result
530526 start = datetime .now ()
531527 diff = timedelta (seconds = timeout )
532528 while datetime .now () - start < diff :
533529 try :
534- self .login () # Awake yet?
535- return result
530+ self .login ()
531+ if not self .restart_required :
532+ return result
536533 except Exception , e :
537- sleep (2 )
538- raise Exception , "Operation timed out."
534+ sleep (1 )
535+ raise Exception , "Operation time out."
539536
540537 @property
541538 def restart_required (self ):
@@ -1179,10 +1176,11 @@ def __getitem__(self, key):
11791176 # x[a,b] is translated to x.__getitem__( (a,b) ), so we
11801177 # have to extract values out.
11811178 key , ns = key
1179+ key = UrlEncoded (key , encode_slash = True )
11821180 response = self .get (key , owner = ns .owner , app = ns .app )
11831181 else :
1182+ key = UrlEncoded (key , encode_slash = True )
11841183 response = self .get (key )
1185-
11861184 entries = self ._load_list (response )
11871185 if len (entries ) > 1 :
11881186 raise AmbiguousReferenceException ("Found multiple entities named '%s'; please specify a namespace." % key )
@@ -1216,6 +1214,7 @@ def __iter__(self, **kwargs):
12161214 for entity in saved_searches:
12171215 print "Saved search named %s" % entity.name
12181216 """
1217+
12191218 for item in self .iter (** kwargs ):
12201219 yield item
12211220
@@ -1423,6 +1422,8 @@ def list(self, count=None, **kwargs):
14231422 return list (self .iter (count = count , ** kwargs ))
14241423
14251424
1425+
1426+
14261427class Collection (ReadOnlyCollection ):
14271428 """A collection of entities.
14281429
@@ -1468,6 +1469,7 @@ class Collection(ReadOnlyCollection):
14681469 :class:`Collection` does no caching. Each call makes at least one
14691470 round trip to the server to fetch data.
14701471 """
1472+
14711473 def create (self , name , ** params ):
14721474 """Creates a new entity in this collection.
14731475
@@ -1542,6 +1544,7 @@ def delete(self, name, **params):
15421544 saved_searches.delete('my_saved_search')
15431545 assert 'my_saved_search' not in saved_searches
15441546 """
1547+ name = UrlEncoded (name , encode_slash = True )
15451548 if 'namespace' in params :
15461549 namespace = params .pop ('namespace' )
15471550 params ['owner' ] = namespace .owner
@@ -1559,6 +1562,56 @@ def delete(self, name, **params):
15591562 raise
15601563 return self
15611564
1565+ def get (self , name = "" , owner = None , app = None , sharing = None , ** query ):
1566+ """Performs a GET request to the server on the collection.
1567+
1568+ If *owner*, *app*, and *sharing* are omitted, this method takes a
1569+ default namespace from the :class:`Service` object for this :class:`Endpoint`.
1570+ All other keyword arguments are included in the URL as query parameters.
1571+
1572+ :raises AuthenticationError: Raised when the ``Service`` is not logged in.
1573+ :raises HTTPError: Raised when an error in the request occurs.
1574+ :param path_segment: A path segment relative to this endpoint.
1575+ :type path_segment: ``string``
1576+ :param owner: The owner context of the namespace (optional).
1577+ :type owner: ``string``
1578+ :param app: The app context of the namespace (optional).
1579+ :type app: ``string``
1580+ :param sharing: The sharing mode for the namespace (optional).
1581+ :type sharing: "global", "system", "app", or "user"
1582+ :param query: All other keyword arguments, which are used as query
1583+ parameters.
1584+ :type query: ``string``
1585+ :return: The response from the server.
1586+ :rtype: ``dict`` with keys ``body``, ``headers``, ``reason``,
1587+ and ``status``
1588+
1589+ Example:
1590+
1591+ import splunklib.client
1592+ s = client.service(...)
1593+ saved_searches = s.saved_searches
1594+ saved_searches.get("my/saved/search") == \\
1595+ {'body': ...a response reader object...,
1596+ 'headers': [('content-length', '26208'),
1597+ ('expires', 'Fri, 30 Oct 1998 00:00:00 GMT'),
1598+ ('server', 'Splunkd'),
1599+ ('connection', 'close'),
1600+ ('cache-control', 'no-store, max-age=0, must-revalidate, no-cache'),
1601+ ('date', 'Fri, 11 May 2012 16:30:35 GMT'),
1602+ ('content-type', 'text/xml; charset=utf-8')],
1603+ 'reason': 'OK',
1604+ 'status': 200}
1605+ saved_searches.get('nonexistant/search') # raises HTTPError
1606+ s.logout()
1607+ saved_searches.get() # raises AuthenticationError
1608+
1609+ """
1610+ name = UrlEncoded (name , encode_slash = True )
1611+ return super (Collection , self ).get (name , owner , app , sharing , ** query )
1612+
1613+
1614+
15621615
15631616class ConfigurationFile (Collection ):
15641617 """This class contains all of the stanzas from one configuration file.
@@ -2075,27 +2128,32 @@ def __init__(self, service, kindmap=None):
20752128 Collection .__init__ (self , service , PATH_INPUTS , item = Input )
20762129
20772130 def __getitem__ (self , key ):
2131+ # The key needed to retrieve the input needs it's parenthesis to be URL encoded
2132+ # based on the REST API for input
2133+ # <http://docs.splunk.com/Documentation/Splunk/latest/RESTAPI/RESTinput>
20782134 if isinstance (key , tuple ) and len (key ) == 2 :
20792135 # Fetch a single kind
20802136 key , kind = key
2137+ key = UrlEncoded (key , encode_slash = True )
20812138 try :
20822139 response = self .get (self .kindpath (kind ) + "/" + key )
20832140 entries = self ._load_list (response )
20842141 if len (entries ) > 1 :
20852142 raise AmbiguousReferenceException ("Found multiple inputs of kind %s named %s." % (kind , key ))
20862143 elif len (entries ) == 0 :
2087- raise KeyError ((kind , key ))
2144+ raise KeyError ((key , kind ))
20882145 else :
20892146 return entries [0 ]
20902147 except HTTPError as he :
20912148 if he .status == 404 : # No entity matching kind and key
2092- raise KeyError ((kind , key ))
2149+ raise KeyError ((key , kind ))
20932150 else :
20942151 raise
20952152 else :
20962153 # Iterate over all the kinds looking for matches.
20972154 kind = None
20982155 candidate = None
2156+ key = UrlEncoded (key , encode_slash = True )
20992157 for kind in self .kinds :
21002158 try :
21012159 response = self .get (kind + "/" + key )
@@ -2114,7 +2172,7 @@ def __getitem__(self, key):
21142172 else :
21152173 raise
21162174 if candidate is None :
2117- raise KeyError (key ) # Never found a match
2175+ raise KeyError (key ) # Never found a match.
21182176 else :
21192177 return candidate
21202178
@@ -2187,11 +2245,14 @@ def create(self, name, kind, **kwargs):
21872245 # If we created an input with restrictToHost set, then
21882246 # its path will be <restrictToHost>:<name>, not just <name>,
21892247 # and we have to adjust accordingly.
2248+
2249+ # Url encodes the name of the entity.
2250+ name = UrlEncoded (name , encode_slash = True )
21902251 path = _path (
21912252 self .path + kindpath ,
21922253 '%s:%s' % (kwargs ['restrictToHost' ], name ) \
21932254 if kwargs .has_key ('restrictToHost' ) else name
2194- )
2255+ )
21952256 return Input (self .service , path , kind )
21962257
21972258 def delete (self , name , kind = None ):
@@ -2268,7 +2329,7 @@ def itemmeta(self, kind):
22682329 def _get_kind_list (self , subpath = None ):
22692330 if subpath is None :
22702331 subpath = []
2271-
2332+
22722333 kinds = []
22732334 response = self .get ('/' .join (subpath ))
22742335 content = _load_atom_entries (response )
@@ -2326,12 +2387,12 @@ def kindpath(self, kind):
23262387 :rtype: ``string``
23272388 """
23282389 if kind in self .kinds :
2329- return kind
2390+ return UrlEncoded ( kind , skip_encode = True )
23302391 # Special cases
23312392 elif kind == 'tcp' :
2332- return 'tcp/raw'
2393+ return UrlEncoded ( 'tcp/raw' , skip_encode = True )
23332394 elif kind == 'splunktcp' :
2334- return 'tcp/cooked'
2395+ return UrlEncoded ( 'tcp/cooked' , skip_encode = True )
23352396 else :
23362397 raise ValueError ("No such kind on server: %s" % kind )
23372398
@@ -2398,6 +2459,7 @@ def list(self, *kinds, **kwargs):
23982459 path = self .kindpath (kind )
23992460 logging .debug ("Path for inputs: %s" , path )
24002461 try :
2462+ path = UrlEncoded (path , skip_encode = True )
24012463 response = self .get (path , ** kwargs )
24022464 except HTTPError , he :
24032465 if he .status == 404 : # No inputs of this kind
@@ -2422,6 +2484,7 @@ def list(self, *kinds, **kwargs):
24222484 for kind in kinds :
24232485 response = None
24242486 try :
2487+ kind = UrlEncoded (kind , skip_encode = True )
24252488 response = self .get (self .kindpath (kind ), search = search )
24262489 except HTTPError as e :
24272490 if e .status == 404 :
0 commit comments