def delete_all(self, session_key, owner, objecttype, filter_data, host_base_uri=''): """ Implements a bulk delete based on the object type and the other filter data that comes in. While you can put in things without filterdata, in this case I am requiring it to not be empty. Because that's how you delete your entire environment, and that isn't good """ LOG_PREFIX = "[delete_all_statestore_" + objecttype + "]" if filter_data is None or len(filter_data) == 0: logger.error(LOG_PREFIX + "filter data required for batch delete") return # ITOA-2913 if "" in filter_data: message = _("Empty field received - Rejecting filter.") logger.error(LOG_PREFIX + message) raise StateStoreError(message) uri = host_base_uri + "/servicesNS/" + owner + "/" + self.app + "/storage/collections/data/" + self.collectionname filter_data['object_type'] = objecttype get_args = {} try: get_args['query'] = json.dumps(filter_data) except ValueError: logger.exception( "error parsing json of query - aborting request - query=%s", filter_data) return try: #get_args are used here because those are appended to the URI - so they work in this case for deletes response, content = rest.simpleRequest(uri, method="DELETE", sessionKey=session_key, getargs=get_args, raiseAllErrors=False) del content except ResourceNotFound: #We tried to delete something that doesnt exist, just continue logger.exception( "error parsing json of query - aborting request - query=%s", filter_data) return #Here we're being generous about what we choose to accept if response.status != 200: logger.error(LOG_PREFIX + "could not batch delete status={0} query={1}".format( response.status, filter_data)) return
def _execute_save_request(self, session_key, owner, data, host_base_uri=''): """ Executes a save request for the specified data @param session_key: splunkd session key @type session_key: string @param owner: splunkd session key @type session_key: string @param data: The dict data (suitable for json-ification) @type data: dict @rtype: dict @return: return value from the save request @param host_base_uri: The host to run action. '' defaults to localhost @type host_base_uri: string """ LOG_PREFIX = "[batch_save]" uri = "{}/servicesNS/{}/{}/storage/collections/data/{}/batch_save".format( host_base_uri, owner, self.app, self.collectionname) response, content = rest.simpleRequest(uri, method="POST", jsonargs=json.dumps(data), sessionKey=session_key, raiseAllErrors=False) if response.status not in (200, 201): details = StateStore._extract_content_error(content) message = _( "Batch save to KV store failed with code {0}. Error details: {1}" ).format(response.status, details) logger.error('%s %s. Response=`%s`. Content=`%s`', LOG_PREFIX, message, response, content) raise StateStoreError(message) try: # What we'll get back here is of the form {"_key":"somelongnumber"} (note the quotes) parsed_content = json.loads(content) return parsed_content except TypeError: message = _( "Unable to parse batch response from statestore for batch_edit" ) logger.exception(LOG_PREFIX + message) raise StateStoreError(message)
def lazy_init(self, session_key, host_base_uri=''): ''' Query the kvstore config uri with the collection name, initialize the collection if 404 exception is returned, otherwise pass. @param session_key: The splunkd session key @type session_key: string @param host_base_uri: The base uri <scheme>://<host>:<port> of the target host. '' targets local host. @type host_base_uri: basestring ''' LOG_PREFIX = "[lazy_init] " entries = [] uri = "{}/servicesNS/{}/{}/storage/collections/config/{}".format( host_base_uri, self.owner, self.app, self.collectionname) try: response, content = rest.simpleRequest( uri, getargs={"output_mode": "json"}, sessionKey=session_key, raiseAllErrors=False) parsed_content = json.loads(content) entries = parsed_content.get('entry', []) except ResourceNotFound: logger.debug( '%s does not exist, it could be a new collection, will try to create it.', self.collectionname) except Exception as e: logger.exception(str(e)) if len(entries) == 0: #If it doesnt have the collection, we need to create it postargs = {"name": self.collectionname} postargs["output_mode"] = "json" response, content = rest.simpleRequest(uri, method="POST", postargs=postargs, sessionKey=session_key, raiseAllErrors=False) if response.status != 200 and response.status != 201: logger.error( "%s Unable to create collection=`%s`. URL=`%s`. Response=`%s`. Content=`%s`", LOG_PREFIX, self.collectionname, uri, response, content) else: logger.debug("%s Created collection successfully.", LOG_PREFIX)
def create(self, session_key, owner, objecttype, data, host_base_uri=''): """ Create accepts different entity specifiers here, but can be reporposed for other collection tasks @param session_key: splunkd session key @type session_key: string @param objecttype: The type of object we are attempting to create @type objecttype: string @param data: The dict data (suitable for json-ification) @type data: dict @param host_base_uri: The host to run the rest request. '' defaults to localhost @type host_base_uri: string """ LOG_PREFIX = "[create_statestore_" + objecttype + "]" #Build the request uri = "{}/servicesNS/{}/{}/storage/collections/data/{}".format( host_base_uri, owner, self.app, self.collectionname) data['object_type'] = objecttype response, content = rest.simpleRequest(uri, method="POST", jsonargs=json.dumps(data), sessionKey=session_key, raiseAllErrors=False) if response.status != 200 and response.status != 201: #Something failed in our request, raise an error message = _("Unable to save {0}, request failed. ").format( objecttype) logger.error('%s %s. response=`%s` content=`%s`', LOG_PREFIX, message, response, content) raise StateStoreError(content, response.status) try: #What we'll get back here is of the form {"_key":"somelongnumber"} (note the quotes) parsed_content = json.loads(content) return parsed_content except TypeError: message = _("Unable to parse response from statestore for {0} {1}." ).format(objecttype, data) logger.exception(LOG_PREFIX + message) raise StateStoreError(message)
def get(self, session_key, owner, objecttype, identifier, sort_key=None, sort_dir=None, filter_data={}, fields=None, skip=None, limit=None, host_base_uri=''): """ Retrieves the object specified by the identifier, which can be either internal or external @param session_key: The splunkd session key @type session_key: str @param objecttype: The type of object (currently service or entity) @type objecttype: str @param identifier: The object's primary identifier, if None retrieves all objects of the selected type @type identifier: str or None @param sort_key: the field on which to ask the server to sort the results @type sort_key: str @param fields: An array of fields to be returned. This is an array that is used to limit the field set returned @type fields: list @param host_base_uri: The host to to run the rest request. '' defaults to localhost @type host_base_uri: string """ LOG_PREFIX = "[get_statestore_" + objecttype + "]" uri = host_base_uri + "/servicesNS/" + owner + "/" + self.app + "/storage/collections/data/" + self.collectionname get_args = None if identifier is None: filter_data = {} if filter_data is None else filter_data endpoint = uri #Here we plan on getting all elements #Pass in the filter_data and use the sort_key,sort_dir if defined if sort_key != None and sort_dir is None: logger.error(LOG_PREFIX + "sort_key defined as {0} with no sort direction". format(sort_key)) elif sort_key is None and sort_dir != None: logger.error(LOG_PREFIX + "sort_dir defined as {0} with no sort key".format( sort_dir)) elif sort_key != None and sort_dir != None: if sort_dir == "desc": sort_dir = -1 else: sort_dir = 1 #Default to ascending get_args = {"sort": sort_key + ":" + str(sort_dir)} filter_data["object_type"] = objecttype #ITOA-2913 if "" in filter_data: message = _("Empty field received - Rejecting filter.") logger.error(LOG_PREFIX + message) raise StateStoreError(message) if "filter_string" in filter_data: logger.debug(LOG_PREFIX + "filter_string=%s", filter_data["filter_string"]) filter_data.update(filter_data["filter_string"]) del filter_data["filter_string"] if "shared" in filter_data: get_args = {} if get_args is None else get_args get_args["shared"] = filter_data["shared"] del filter_data["shared"] if fields is not None: get_args = {} if get_args is None else get_args if "object_type" not in fields: fields.append("object_type") exclude = [field for field in fields if ':0' in field] # Mongodb does not allow field inclusion and exclusion in a single query. # The assumption is that if there is more than one field exclusion, # the system will ignore the field inclusion. if len(exclude) > 0: final_fields = exclude else: final_fields = fields get_args['fields'] = ','.join(final_fields) if skip is not None: get_args = {} if get_args is None else get_args get_args['skip'] = skip if limit is not None: get_args = {} if get_args is None else get_args get_args['limit'] = limit # At this point, 'filter_data' should have only the data for the 'query' param # Other params should be stored in 'get_args' and deleted from 'filter_data' if len(filter_data) > 0: get_args = {} if get_args is None else get_args try: get_args['query'] = json.dumps(filter_data) logger.debug( LOG_PREFIX + "json.dumps successful, get_args=%s", get_args) except ValueError: logger.error( LOG_PREFIX + "error parsing json of query - query=%s", filter_data) else: endpoint = uri + "/" + quote_plus(identifier) content = "[]" for retry in range(3): try: response, content = rest.simpleRequest(endpoint, method="GET", sessionKey=session_key, raiseAllErrors=True, getargs=get_args, timeout=REST_TIMEOUT) if 300 > response.status > 199: break except ResourceNotFound: logger.error("%s 404 Not Found on GET to %s", LOG_PREFIX, endpoint) # Return None when something is not found return None except RESTException as e: if e.statusCode == 503 and retry != 2: logger.warn( "%s status 503 on endpoint %s, assuming KV Store starting up and retrying request, retries=%s", LOG_PREFIX, endpoint, retry) import time time.sleep(2) else: logger.error( "%s status %s on endpoint %s, raising exception", LOG_PREFIX, e.statusCode, endpoint) raise try: parsed_content = json.loads(content) if len(parsed_content) == 0: parsed_content = {} return parsed_content except TypeError: message = _("Unable to parse response from statestore for {0} {1}." ).format(objecttype, identifier) logger.exception(LOG_PREFIX + message) raise StateStoreError(message) except ValueError: message = _( "Unable to decode response from statestore for {0} {1}." ).format(objecttype, identifier) logger.exception(LOG_PREFIX + message) raise StateStoreError(message)