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

# 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))

Powered by BW's shoe-string budget.