You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
12 KiB
247 lines
12 KiB
# coding=UTF-8
|
|
|
|
from __future__ import absolute_import
|
|
|
|
from builtins import range
|
|
import splunk
|
|
from splunk import auth, entity, rest, util
|
|
|
|
from future.moves.urllib import parse as urllib_parse
|
|
|
|
import splunk.search # we cant import it more simply as 'search' because it collides with several 'search' arguments in these functions.
|
|
import splunk.safe_lxml_etree as et
|
|
import logging
|
|
import splunk.util
|
|
logger = logging.getLogger('splunk.saved')
|
|
|
|
|
|
# Saved Search constants
|
|
SAVED_SEARCHES_ENDPOINT_ENTITY_PATH = 'saved/searches'
|
|
SAVED_SEARCHES_HISTORY_ENTITY_PATH = 'saved/searches/%s/history'
|
|
SAVED_SEARCH_ESCAPE_CHR = u'"'
|
|
|
|
SAVED_SEARCH_DISPATCH_ARG_MAP = {
|
|
'ttl': 'dispatch.ttl',
|
|
'buckets': 'dispatch.buckets',
|
|
'maxCount': 'dispatch.max_count',
|
|
'maxTime': 'dispatch.max_time',
|
|
'lookups': 'dispatch.lookups',
|
|
'spawnProcess': 'dispatch.spawn_process',
|
|
'timeFormat': 'dispatch.time_format',
|
|
'earliestTime': 'dispatch.earliest_time',
|
|
'latestTime': 'dispatch.latest_time'
|
|
}
|
|
|
|
|
|
# ////////////////////////////////////////////////////////////////////////////
|
|
# Saved Search functions
|
|
# ////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
def dispatchSavedSearch(savedSearchName, sessionKey=None, namespace=None, owner=None, hostPath=None, now=0, triggerActions=0, **kwargs):
|
|
"""Initiates a new job based on a saved search."""
|
|
|
|
uri = entity.buildEndpoint(['saved', 'searches', savedSearchName, 'dispatch'], namespace=namespace, owner=owner)
|
|
if hostPath:
|
|
uri = splunk.mergeHostPath(hostPath) + uri
|
|
|
|
args = {
|
|
'now': now,
|
|
'trigger_actions' : triggerActions
|
|
}
|
|
|
|
for key, val in kwargs.items():
|
|
if key in SAVED_SEARCH_DISPATCH_ARG_MAP:
|
|
args[SAVED_SEARCH_DISPATCH_ARG_MAP[key]] = val
|
|
# Pass through for dispatch.* formated kwargs
|
|
elif key.startswith('dispatch.'):
|
|
args[key] = val
|
|
|
|
serverResponse, serverContent = rest.simpleRequest(uri, postargs=args, sessionKey=sessionKey)
|
|
root = et.fromstring(serverContent)
|
|
|
|
# normal messages from splunkd are propogated via SplunkdException;
|
|
if not 201 == serverResponse.status:
|
|
|
|
extractedMessages = rest.extractMessages(root)
|
|
for msg in extractedMessages:
|
|
raise splunk.SearchException(msg['text'])
|
|
|
|
# get the search ID
|
|
sid = root.findtext('sid').strip()
|
|
|
|
# instantiate result object
|
|
return splunk.search.SearchJob(sid, hostPath, sessionKey, namespace, owner)
|
|
|
|
|
|
def createSavedSearch(search, label, sessionKey=None, namespace=None, owner=None, earliestTime=None, latestTime=None, hostPath=None):
|
|
'''Save a search given a search string and a search label.'''
|
|
|
|
output = entity.Entity(SAVED_SEARCHES_ENDPOINT_ENTITY_PATH, label, namespace=namespace, owner=owner)
|
|
|
|
# Manually set the properties...
|
|
output['search'] = search
|
|
output['name'] = label
|
|
output['dispatch.earliest_time'] = earliestTime
|
|
output['dispatch.latest_time'] = latestTime
|
|
if hostPath:
|
|
output.hostPath = hostPath
|
|
entity.setEntity(output, sessionKey=sessionKey)
|
|
return output
|
|
|
|
|
|
def getSavedSearchWithTimes(label, et, lt, namespace=None, sessionKey=None, owner=None, hostPath=None):
|
|
'''Retrieve a list of UTC times that a saved searches was schedule to run.'''
|
|
return entity.getEntity(SAVED_SEARCHES_ENDPOINT_ENTITY_PATH, label, uri="/servicesNS/"+owner+"/"+namespace+"/saved/searches/"+urllib_parse.quote_plus(label)+"/scheduled_times", earliest_time=et, latest_time=lt, namespace=namespace, owner=owner, sessionKey=sessionKey, hostPath=hostPath)
|
|
|
|
def listSavedSearches(namespace=None, sessionKey=None, owner=None, hostPath=None, count=None):
|
|
'''Retrieve a list of saved searches.'''
|
|
return entity.getEntities(SAVED_SEARCHES_ENDPOINT_ENTITY_PATH, namespace=namespace, owner=owner, sessionKey=sessionKey, hostPath=hostPath, count=count)
|
|
|
|
|
|
def getSavedSearch(label, namespace=None, sessionKey=None, owner=None, hostPath=None, uri=None):
|
|
'''Retrieve a single saved search.'''
|
|
return entity.getEntity(SAVED_SEARCHES_ENDPOINT_ENTITY_PATH, label, namespace=namespace, owner=owner, sessionKey=sessionKey, hostPath=hostPath, uri=uri)
|
|
|
|
|
|
def getSavedSearchHistory(label, namespace=None, sessionKey=None, ignoreExpired=True, owner=None, sortKey=None, sortDir='desc', ignoreRunning=False, search=None, hostPath=None, uri=None):
|
|
'''
|
|
Retrieve the history of a saved search.
|
|
|
|
ignoreExpired: {True|False} When True only return saved searches that have a TTL > 0 or they have been saved by the user.
|
|
When False return everything, including searches with a TTL >= 0 regardless of the job's saved status.
|
|
'''
|
|
#SPL-35662 done RT searches are alert artifacts which should be of no use to UI,
|
|
# dashboards should always reference running rt searches
|
|
search = (search if search is not None else '') + " NOT (isRealTimeSearch=1 AND isDone=1)"
|
|
entities = entity.getEntities(['saved', 'searches', label, 'history'], namespace=namespace, owner=owner, sessionKey=sessionKey, search=search, hostPath=hostPath, count=0, uri=uri)
|
|
|
|
if ignoreExpired or ignoreRunning:
|
|
for sid, job in entities.items():
|
|
if ignoreExpired \
|
|
and splunk.search.normalizeJobPropertyValue('ttl', job.get('ttl')) <= 0 \
|
|
and not splunk.search.normalizeJobPropertyValue('isSaved', job.get('isSaved')):
|
|
del entities[sid]
|
|
continue
|
|
# real time searches are never done so no need to ignore running
|
|
isRTSearch = splunk.search.normalizeJobPropertyValue('isRealTimeSearch', job.get('isRealTimeSearch'))
|
|
isDone = splunk.search.normalizeJobPropertyValue('isDone', job.get('isDone'))
|
|
if ignoreRunning and not isDone and not isRTSearch:
|
|
del entities[sid]
|
|
|
|
if sortKey:
|
|
reverse = True
|
|
if sortDir == 'asc':
|
|
reverse = False
|
|
try:
|
|
entities = sorted(entities.items(), key=lambda x: x[1].__dict__[sortKey], reverse=reverse)
|
|
except KeyError as e:
|
|
logger.warning("Attempted to sort on a key (%s) from a saved search's history that doesn't exist" % sortKey)
|
|
|
|
return util.OrderedDict(entities)
|
|
|
|
def getSavedSearchJobs(label, namespace=None, owner=None, ignoreExpired=True, ignoreRunning=False, sortKey='createTime', sortDir='desc', sessionKey=None, hostPath=None, **kw):
|
|
'''Retrieve the saved search's history as search.SearchJob objects.'''
|
|
|
|
jobs = []
|
|
history = getSavedSearchHistory(label, namespace=namespace, owner=owner, ignoreExpired=ignoreExpired, ignoreRunning=ignoreRunning, sortKey=sortKey, sortDir=sortDir, sessionKey=sessionKey, hostPath=hostPath)
|
|
for sid in history:
|
|
jobs.append(splunk.search.getJob(sid, hostPath=hostPath, sessionKey=sessionKey))
|
|
return jobs
|
|
|
|
|
|
def getJobForSavedSearch(label, useHistory=None, namespace=None, sessionKey=None, ignoreExpired=True, owner=None, ignoreRunning=True, sortKey='createTime', sortDir='desc', search=None, hostPath=None, **kw):
|
|
'''
|
|
Retrieve the last job run for a saved search.
|
|
|
|
== WARNING ==
|
|
This is meant to be a convenience method for accessing jobs from saved searches that
|
|
are typically run by the splunkd scheduler. As such dispatching a job from a saved search
|
|
and then attempting to immediately call getJobForSavedSearch with the param
|
|
ignoreRunning == True will result in a second job being dispatched.
|
|
== / WARNING ==
|
|
|
|
useHistory dictates how getJobForSavedSearch attempts to fetch a job.
|
|
useHistory=None implies that if the saved search has a history of jobs relevant to
|
|
the saved search, it will return the last run saved search. If no jobs can be
|
|
found a new one will be dispatched and returned.
|
|
useHistory=True implies that the last run job for the saved search will be returned.
|
|
If no jobs exist None will be returned instead.
|
|
useHistory=False is effectively the same as calling dispatchSavedSearch(label) in that
|
|
it does not check for a previously run job, and instead forces a new job to be
|
|
created and returned. This option is left for convenience.
|
|
'''
|
|
job = None
|
|
useHistory = util.normalizeBoolean(useHistory)
|
|
if isinstance(useHistory, splunk.util.string_type):
|
|
#verified while fixing SPL-47422
|
|
#pylint: disable=E1103
|
|
if useHistory.lower() in ('none', 'auto'):
|
|
useHistory = None
|
|
else:
|
|
raise ValueError('Invalid option passed for useHistory: %s' % useHistory)
|
|
|
|
logger.debug('getJobForSavedSearch - label=%s namespace=%s owner=%s' % (label, namespace, owner))
|
|
|
|
# Attempt to get the saved search history
|
|
if useHistory is None or useHistory is True:
|
|
history = getSavedSearchHistory(label, namespace=namespace, sessionKey=sessionKey, ignoreExpired=ignoreExpired, owner=owner, ignoreRunning=ignoreRunning, sortKey=sortKey, sortDir=sortDir, search=search, hostPath=hostPath, uri=kw.get('historyURI'))
|
|
if len(history) > 0:
|
|
job = splunk.search.getJob(list(history.keys())[0], hostPath=hostPath, sessionKey=sessionKey)
|
|
logger.debug('getJobForSavedSearch - found job artifact sid=%s' % job.id)
|
|
|
|
# Dispatch a new search if there is no history for the search
|
|
if (useHistory is False) or (useHistory is None and job is None):
|
|
logger.debug('getJobForSavedSearch - no artifact found; dispatching new job')
|
|
job = dispatchSavedSearch(label, sessionKey=sessionKey, namespace=namespace, owner=owner, hostPath=hostPath, **kw)
|
|
|
|
# If the user specified useHistory = yes and no history was found, this may return None
|
|
return job
|
|
|
|
|
|
def deleteSavedSearch(label, namespace=None, sessionKey=None, owner=None, hostPath=None):
|
|
'''Delete a saved search.'''
|
|
return entity.deleteEntity(SAVED_SEARCHES_ENDPOINT_ENTITY_PATH, label, namespace=namespace, owner=owner, sessionKey=sessionKey, hostPath=hostPath)
|
|
|
|
|
|
def getSavedSearchFromSID(sid, sessionKey=None, hostPath=None):
|
|
'''
|
|
Takes a search job id and attempts to find the associated saved search
|
|
object, if set. Returns a splunk.entity.Entity() object if found, None
|
|
otherwise.
|
|
'''
|
|
|
|
job = splunk.search.getJob(sid, sessionKey=sessionKey, hostPath=hostPath)
|
|
|
|
# the eai key contains a : which makes python accessors unhappy; use alt means
|
|
jobProps = job.toJsonable()
|
|
namespace = jobProps['eai:acl']['app']
|
|
owner = jobProps['eai:acl']['owner']
|
|
|
|
if job.isSavedSearch and len(job.label) > 0:
|
|
|
|
# first try to fetch saved search from explicit user container
|
|
try:
|
|
return getSavedSearch(job.label, namespace=namespace, owner=owner, sessionKey=sessionKey, hostPath=hostPath)
|
|
except:
|
|
pass
|
|
|
|
# if fail, try from shared context
|
|
try:
|
|
return getSavedSearch(job.label, namespace=namespace, owner=entity.EMPTY_OWNER_NAME, sessionKey=sessionKey, hostPath=hostPath)
|
|
except splunk.ResourceNotFound:
|
|
pass
|
|
# else raise any other exception
|
|
|
|
return None
|
|
|
|
def savedSearchJSONIsAlert(savedSearchJSON):
|
|
content = savedSearchJSON['entry'][0]['content']
|
|
is_scheduled = util.normalizeBoolean(content['is_scheduled'])
|
|
alert_type = content['alert_type']
|
|
alert_track = util.normalizeBoolean(content['alert.track'])
|
|
actions = util.normalizeBoolean(content.get('actions'))
|
|
isRealtime = content['dispatch.earliest_time'].startswith('rt') and content['dispatch.latest_time'].startswith('rt')
|
|
|
|
return is_scheduled and ((alert_type != 'always') or alert_track or (isRealtime and actions))
|