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.

879 lines
32 KiB

# Copyright (C) 2005-2025 Splunk Inc. All Rights Reserved.
"""
Use this module to work with your Events in ITSI!
Due to dependency on a Splunk ITSI module, this module must reside on the same
host where ITSI is currently installed. Eventually, we will be moving to
consuming standard REST interfaces and then it should be OK to move this module
to a different host.
"""
import itsi_py3
import sys
import json
import time
from copy import deepcopy
from splunk.clilib.bundle_paths import make_splunkhome_path
from splunk import ResourceNotFound, RESTException
sys.path.append(make_splunkhome_path(['etc', 'apps', 'SA-ITOA', 'lib', 'ITOA']))
from ITOA.event_management.notable_event_tag import NotableEventTag
from ITOA.event_management.notable_event_comment import NotableEventComment
from ITOA.itoa_common import extract
from ITOA.event_management.notable_event_ticketing import ExternalTicket
sys.path.append(make_splunkhome_path(['etc', 'apps', 'SA-ITOA', 'lib', 'itsi', 'event_management']))
from itsi_notable_event_group import ItsiNotableEventGroup
from .eventing_base import EventBase
from .eventing import EventMeta
########
# Setup a default Logger
########
from ITOA.setup_logging import logger
########
# APIs
########
class GroupMeta(EventMeta):
"""
Import this class to get information about ITSI Episodes.
- What are all the available status values I can set on an Episode?
- What are all the available owners I can have for an Episode?
- What are all the available severities I can set on an Episode?
Usage:
>>> meta = GroupMeta(session_key) # or
>>> meta = GroupMeta(None, username, password)
Provide your own logger if you want to, else we'll default to default_logger
"""
pass
class EventGroup(EventBase):
"""
Import this class to operate on ITSI Event Group.
"""
def __init__(self, session_key, username=None, password=None, logger=logger):
"""
@type session_key: string
@param session_key: session key given by Splunkd when you log in
If you dont have one, pass in None. But you will have to provide
credentials
@type username: string
@param username: your username
@type password: string
@param password: your password
@type logger: object of type logger
@param logger: if you have an existing logger, pass it in, we'll log
stuff there...else check ITSI logs
"""
super(EventGroup, self).__init__(session_key, username, password, logger)
self.group = ItsiNotableEventGroup(self.session_key, action_dispatch_config=self.action_dispatch_config)
def _get_from_index(self, group_ids, split_by="," , **kwargs):
"""
given an id or list of ids, return a dictionary of pertinent fields
for that group. This method runs a Splunk search.
@type group_id: str
@param group_id: a unique id for a group
@type split_by: str
@param split_by: if group_ids is of type basestring, we will split it
into many group ids into a list. What is the separator? Defaults to `,`
@type kwargs: dict
@param kwargs: send in keys `earliest_time` and
`latest_time` with corresponding values if you know what you are doing.
Ex:
'-15m' implies '15 mins ago'
'-15s' implies '15 seconds ago'
'-15d' implies '15 days ago'
'-15w' implies '15 weeks ago'
'rt' implies real time
'now' implies current time
no other values are supported
@return a valid Dict if event is found; None if no resource is found
"""
if isinstance(group_ids, itsi_py3.string_type):
group_ids = group_ids.split(',')
if not isinstance(group_ids, list):
raise TypeError('Expecting group_ids to be a string or list')
# we only care about `earliest_time` and `latest_time`. get rid of other
# keys
supported_params = ('earliest_time', 'latest_time')
for i in list(kwargs.keys()):
kwargs.pop(i) if i not in supported_params else None
objects = None
# indexing takes time and just because we have a group_id does not
# really means that event has been indexed. we shall therefore
# retry..yes, 10 times.
retries = 10
while retries:
time.sleep(5)
try:
objects = self.group.get_bulk(group_ids)
if objects:
break
retries -= 1
except ResourceNotFound:
self.logger.exception('Unable to find group_id: %s', group_ids)
break
except Exception:
self.logger.exception('Internal Error.')
break
return objects
def get_severity(self, group_ids=None, split_by=",", **kwargs):
"""
given a list of group ids, return their severities
@type group_ids: str or list
@param group_ids: a unique id for a group or a list of them
@type split_by: str
@param split_by: if `group_ids` is of type basestring, we will split it
into many group ids; into a list. What is the separator? Defaults to `,`
@type kwargs: dict
@param kwargs: send in keys `earliest_time` and
`latest_time` with corresponding values if you know what you are doing.
Ex:
'-15m' implies '15 mins ago'
'-15s' implies '15 seconds ago'
'-15d' implies '15 days ago'
'-15w' implies '15 weeks ago'
'rt' implies real time
'now' implies current time
no other values are supported
@return a list of tuples (group_id: severity) on valid request
None on invalid request.
"""
# validate + normalize
if not group_ids:
raise ValueError('Expecting `group_ids`.')
if isinstance(group_ids, itsi_py3.string_type):
group_ids = group_ids.split(split_by)
if not isinstance(group_ids, list):
return None
groups = self._get_from_index(group_ids, kwargs)
if not groups:
return None
severities = extract(groups, 'severity', False)
return list(zip(group_ids, severities))
def get_status(self, group_ids=None, split_by=",", **kwargs):
"""
given a list of group ids, return their statuses
@type group_ids: basestring/list
@param group_ids: a unique id for a group or a list of them
@type split_by: str
@param split_by: if `group_ids` is of type basestring, we will split it
into many event ids; into a list. What is the separator? Defaults to `,`
@type kwargs: dict
@param kwargs: send in keys `earliest_time` and
`latest_time` with corresponding values if you know what you are doing.
Ex:
'-15m' implies '15 mins ago'
'-15s' implies '15 seconds ago'
'-15d' implies '15 days ago'
'-15w' implies '15 weeks ago'
'rt' implies real time
'now' implies current time
no other values are supported
@return a list of tuples (group_id: severity) on valid request
None on an invalid request
"""
# validate + normalize
if not group_ids:
raise ValueError('Expecting `group_ids`.')
if isinstance(group_ids, itsi_py3.string_type):
group_ids = group_ids.split(split_by)
if not isinstance(group_ids, list):
return None
groups = self._get_from_index(group_ids, kwargs)
if not groups:
return None
statuses = extract(groups, 'status', False)
return list(zip(group_ids, statuses))
def get_owner(self, group_ids=None, split_by=",", **kwargs):
"""
given an group id, return its owner
@type group_ids: basestring/list
@param group_ids: a unique id for a group or a list of them
@type split_by: str
@param split_by: if `group_ids` is of type basestring, we will split it
into many group ids; into a list. What is the separator? Defaults to `,`
@type kwargs: dict
@param kwargs: send in keys `earliest_time` and
`latest_time` with corresponding values if you know what you are doing.
Ex:
'-15m' implies '15 mins ago'
'-15s' implies '15 seconds ago'
'-15d' implies '15 days ago'
'-15w' implies '15 weeks ago'
'rt' implies real time
'now' implies current time
no other values are supported
@rtype: list of tuples
@return: [(group_id, owner)] if valid. None if invalid.
"""
# validate + normalize
if not group_ids:
raise ValueError('Expecting `group_ids`.')
if isinstance(group_ids, itsi_py3.string_type):
group_ids = group_ids.split(split_by)
if not isinstance(group_ids, list):
return None
groups = self._get_from_index(group_ids, kwargs)
if not groups:
return None
# extract owners now that we have events...
owners = extract(groups, 'owner', False)
return list(zip(group_ids, owners))
def add_drilldown(self, group_id, drilldown):
"""
Adds drilldown to a notable event group.
@type group_id: string
@param group_id: id of the group where add_drilldown to be operated on
@type drilldown: string
@param drilldown: The drilldown data that wanted to be add to
{
'name': "DrilldownName",
'link': "http://drill.down"
}
"""
return self.group.add_drilldown(group_id, drilldown)
def update_drilldown(self, group_id, drilldown):
"""
Update a drilldown for a NotableEventGroup
@type group_id: string
@param group_id: id of the group who owns the drilldown to be updated
@type drilldown: dict
@param drilldown: drilldown to be updated
"""
return self.group.update_drilldown(group_id, drilldown)
def delete_drilldown(self, group_id, drilldown):
"""
Delete a drilldown for a NotableEventGroup
@type group_id: string
@param group_id: id of the group who owns the drilldown to be deleted
@type drilldown: dict
@param drilldown: drilldown to be deleted
"""
return self.group.delete_drilldown(group_id, drilldown)
def create_tag(self, group_id, tag_value, policy_id=None, raise_exceptions=False):
"""
create a tag for given group_id
@type event_id: str
@param group_id: id of a group
@type tag_value: str
@param tag_value: new value of this tag
@type policy_id: basestring
@param policy_id: policy id of the group
@rtype: dict
@return a nicely formatted dictionary/None on failure.
"""
if not isinstance(group_id, itsi_py3.string_type):
raise TypeError('Expecting `group_id` to be non-empty basestring. Received: %s' % group_id)
if not isinstance(tag_value, itsi_py3.string_type):
raise TypeError('Expecting `tag_value` to be non-empty basestring. Received: %s' % tag_value)
obj = NotableEventTag(self.session_key)
data = {
'event_id': group_id,
'tag_name': tag_value,
'itsi_policy_id': policy_id
}
try:
tag_id = obj.create(data)
except RESTException as e:
if e.statusCode == 409 and not raise_exceptions:
return None
else:
raise
if any([
not isinstance(tag_id, dict),
isinstance(tag_id, dict) and '_key' not in tag_id
]):
self.logger.error('Unable to create requested tag for group_id: `%s`. tag_value: `%s`', group_id, tag_value)
return None
return {
'group_id': group_id,
'tag_id': tag_id['_key'],
'tag_name': tag_value
}
def update_tag(self, group_id, tag_id, tag_value):
"""
given a group id, a tag_id update the existing tag.
A group can have more than one tag.
Each tag has an id. Hence the tag id! For a given group, no duplicate
tags will be allowed.
@type group_id: str
@param group_id: id of a group
@type tag_id: str
@param tag_id: id of the tag
@type tag_value: str
@param tag_value: new value of this tag
@rtype dict:
@return committed value...
"""
if not isinstance(group_id, itsi_py3.string_type):
raise TypeError('Expecting `group_id` to be non-empty basestring. Received: %s' % group_id)
if not isinstance(tag_id, itsi_py3.string_type):
raise TypeError('Expecting `tag_id` to be non-empty basestring. Received: %s' % tag_id)
obj = NotableEventTag(self.session_key)
data = {
'event_id': group_id,
'_key': tag_id,
'tag_name': tag_value
}
rval = obj.update(tag_id, data, is_partial_update=True)
return rval
def get_all_tags(self, group_id):
"""
given an group_id, fetch all of its tags
@type group_id: str
@param group_id: id of a group
@rtype: list
@return: list of all existing tags for a given group
"""
if not isinstance(group_id, itsi_py3.string_type):
raise TypeError('Expecting `group_id` to be non-empty basestring. Received: %s' % group_id)
obj = NotableEventTag(self.session_key)
rval = obj.get(group_id, is_event_id=True)
tags = extract(rval, 'tag_name')
return tags
def get_tag(self, tag_id):
"""
given a tag id, fetch its value
@type tag_id: str
@param tag_id: id of desired tag
@rtype: basestring
@return: tag value corresponding to `tag_id`
"""
if not isinstance(tag_id, itsi_py3.string_type):
raise TypeError('Expecting `tag_id` to be non-empty basestring. Received: %s' % tag_id)
obj = NotableEventTag(self.session_key)
rval = obj.get(tag_id)
tag = extract(rval, 'tag_name')
return tag
def delete_tag(self, tag_id):
"""
given a tag_id, delete its value
@type tag_id: str
@param tag_id: id of the tag you are interested in deleting in.
@return nothing
"""
if not isinstance(tag_id, itsi_py3.string_type):
raise TypeError('Expecting `tag_id` to be non-empty basestring. Received: %s' % tag_id)
obj = NotableEventTag(self.session_key)
obj.delete(tag_id)
return
def delete_all_tags(self, group_id):
"""
given an group_id, delete all of its tags
@type group_id: str
@param group_id: id of the group whose tags you want to delete
@return nothing
"""
if not isinstance(group_id, itsi_py3.string_type):
raise TypeError('Expecting `group_id` to be non-empty basestring. Received: %s' % group_id)
obj = NotableEventTag(self.session_key)
obj.delete(group_id, is_event_id=True)
return
def create_comment(self, group_id, comment, policy_id=None):
"""
for given group_id, add a new comment
@type group_id: str
@param group_id: id of the group you want to add a comment to
@type comment: str
@param comment: the comment you wish to add
@type policy_id: basestring
@param policy_id: policy id of the group
@rtype: dict
@return: a nicely formatted dict consisting of comment, comment id and
event id
"""
if not isinstance(group_id, itsi_py3.string_type):
raise TypeError('Expecting `group_id` to be non-empty basestring. Received: %s' % group_id)
if not isinstance(comment, itsi_py3.string_type):
raise TypeError('Expecting `comment` to be non-empty basestring. Received: %s' % comment)
obj = NotableEventComment(self.session_key)
data = {
'event_id': group_id,
'comment': comment,
'itsi_policy_id': policy_id
}
r = obj.create(data)
if not r:
self.logger.error('Unable to create requested comment `%s` for group id: `%s`', comment, group_id)
return None
rval = {
'event_id': group_id,
'comment_id': r.get('comment_id'),
'comment': comment
}
self.logger.debug('Successfully created comment: `%s` for group id: `%s`. comment id: `%s`',
comment, group_id, r.get('comment_id'))
return rval
def get_comment(self, comment_id):
"""
for a given comment id, fetch the comment
@type comment_id: str
@param comment_id: id of the comment we care about
@rtype: str
@return: comment corresponding to comment id
"""
if not isinstance(comment_id, itsi_py3.string_type):
raise TypeError('Expecting `comment_id` to be non-empty basestring. Received: %s' % comment_id)
obj = NotableEventComment(self.session_key)
res = obj.get(comment_id)
rval = json.loads(res['_raw'])
comment = extract(rval, 'comment')
if isinstance(comment, str):
return comment
else:
return comment[0]
def get_all_comments(self, group_id):
"""
for a given group id, fetch all of its comments
@type group_id: str
@param group_id: id of the group we care about
@rtype: list
@return: list of comments corresponding to group_id
"""
if not isinstance(group_id, itsi_py3.string_type):
raise TypeError('Expecting `group_id` to be non-empty basestring. Received: %s' % group_id)
obj = NotableEventComment(self.session_key)
res = obj.get(group_id, is_group_id=True)
comments = []
for result in res:
rval = json.loads(result['_raw'])
comment = extract(rval, 'comment')
comments.append(comment[0])
return comments
def delete_comment(self, comment_id):
"""
delete the comment associated with comment id
@type comment_id: str
@param comment_id: id of the comment we care about
@returns: nothing
This operation is currently unsupported.
"""
raise NotImplementedError('delete_comment operation is not supported')
def delete_all_comments(self, group_id):
"""
delete all comments associated with group id
@type group_id: str
@param group_id: id of the event we care about
@returns nothing
This operation is currently unsupported.
"""
raise NotImplementedError('delete_all_comments operation is not supported')
def update_comment(self, group_id, comment_id, comment):
"""
given a group id, a comment_id update the comment.
A group can have more than one comment.
Each comment has an id. Hence the event id! For a given event, no duplicate
comments will be allowed.
@type group_id: str
@param group_id: id of a group
@type comment_id: str
@param comment_id: id of the comment
@type comment: str
@param comment: new value of this comment
@rtype dict:
@return committed value...
This operation is currently unsupported.
"""
raise NotImplementedError('update_comment operation is not supported')
def update_ticket_info(self, group_ids, ticket_system, ticket_id,
ticket_url, **other_params):
"""
given a list of group_ids, update each of them with an external
ticket info
@type group_ids: list/basestring
@param group_ids: list of group_ids or comma separated string of
group_ids
@type ticket_system: basestring
@param ticket_system: string representing an external ticket system
Ex: Remedy, Siebel, ServiceNow etc...
@type ticket_id: basestring
@param ticket_id: identifier of an external ticket.
@type ticket_url: basestring
@param ticket_url: url to reach external ticket.
@type other_params: dict
@param other_params: other key value pairs to locate your event. Pass nothing if you dont know these
values.
@rtype dict:
@return: dict with list of ticket ids that are successfully updated
and list of ticket ids that were fail to update.
"""
if isinstance(group_ids, itsi_py3.string_type):
group_ids = group_ids.split(',')
if not isinstance(group_ids, list):
raise TypeError('Expecting group_ids to be a list type. Received=%s.' % type(group_ids).__name__)
if not group_ids:
raise ValueError('Expecting group_ids to have at least 1 id. Received=%s.' % group_ids)
rval = {
'success': [],
'failed': []
}
self.logger.debug(('Group ids=%s ticket_system=%s ticket_id=%s'
' ticket_url=%s other params=%s'), group_ids, ticket_system,
ticket_id, ticket_url, json.dumps(other_params))
for id_ in group_ids:
ticket = ExternalTicket(id_, self.session_key, self.logger)
content = ticket.upsert(ticket_system, ticket_id, ticket_url, **other_params)
if content:
rval['success'].append(ticket_id)
else:
rval['failed'].append(ticket_id)
self.logger.debug('Succeeded for ids=%s Failed for ids=%s', rval['success'], rval['failed'])
return rval
def delete_ticket_info(self, group_ids, ticket_system, ticket_id):
"""
Delete external ticketing based information for given group_ids
@type group_ids: list/basestring
@param group_ids: list of group_ids or comma separated string of
group_ids
@type ticket_system: basestring
@param ticket_system: string representing an external ticket system
Ex: Remedy, Siebel, ServiceNow etc...
Set ticket_system to None to delete all tickets for these event_ids
@type ticket_id: basestring
@param ticket_id: identifier of an external ticket.
Set ticket_id to None to delete all tickets for this ticket_system
@rtype dict:
@return: dict with list of ticket ids that are successfully deleted
and list of ticket ids that were fail to delete.
"""
if isinstance(group_ids, itsi_py3.string_type):
group_ids = group_ids.split(',')
if not isinstance(group_ids, list):
raise TypeError('Expecting group_ids to be a list type. Received=%s.' % type(group_ids).__name__)
if not group_ids:
raise ValueError('Expecting group_ids to have at least 1 id. Received=%s.' % group_ids)
rval = {
'success': [],
'failed': []
}
self.logger.debug(('Group ids=%s ticket_system=%s ticket_id=%s'' ticket_url=%s other params=%s'),
group_ids, ticket_system, ticket_id)
for id_ in group_ids:
ticket = ExternalTicket(id_, self.session_key, self.logger)
ticket.delete(ticket_system, ticket_id)
rval['success'].append(id_)
self.logger.debug('Succeeded for ids=%s Failed for ids=%s', rval['success'], rval['failed'])
return rval
def update(self, blob, split_by=',', **kwargs):
"""
update each group id in `blob` with given data value individually
This method only deals with updating `status`, `severity` and `owner`
To update tags and comments, use `update_tag()` and `update_comment()`
Why separate methods? This is an implementation *secret*.
@type blob: dict
@param blob:
[
{
'group_ids': ['group_id1', 'group_ida', ... ],
'severity': '1',
'status': '5',
'owner': 'cottonmouth'
},
{
'group_ids': ['group_id2', 'event_idb', ... ],
'severity': '6',
'status': '2',
'owner': 'black_mamba'
},
...
]
@type kwargs: dict
@param kwargs: send in keys `earliest_time` and
`latest_time` with corresponding values if you know what you are doing.
Ex:
'-15m' implies '15 mins ago'
'-15s' implies '15 seconds ago'
'-15d' implies '15 days ago'
'-15w' implies '15 weeks ago'
'rt' implies real time
'now' implies current time
no other values are supported
@rtype: dict
@return: dictionary of event_id schemas, in the order of input...
"""
if isinstance(blob, dict):
blob = [blob]
if not isinstance(blob, list):
raise TypeError('Expecting `blob` to be a dict/list. Received: %s' % type(blob).__name__)
if not blob:
raise ValueError('Expecting `blob` to be non-empty.')
rval = []
for group in blob:
# validate/sanitize...
if not isinstance(group, dict):
raise TypeError('Expecting a dictionary. Received: `%s`. Type: `%s`' % (group, type(group).__name__))
if 'group_ids' not in group:
raise KeyError('Expecting `group_ids` in your input.')
keys = group.pop('group_ids')
# sanitize request, get rid of unsupported keys...
supported_keys = ('owner', 'severity', 'status', 'group_ids')
for k in list(group.keys()):
if k not in supported_keys:
self.logger.debug('Getting rid of `%s`: `%s`. Unsupported.' % (k, group[k]))
group.pop(k)
if isinstance(keys, itsi_py3.string_type):
keys = keys.split(split_by)
data = []
for i in keys:
group_data = deepcopy(group)
group_data['group_id'] = i
data.append(group_data)
kwargs['is_partial_update'] = True
self.logger.debug('Updating keys: `%s` with: %s. kwargs: %s', keys, data, kwargs)
objects = self.group.update_bulk(keys, data, kwargs)
rval.extend(objects)
return rval
def update_severity(self, group_ids, severity, split_by=',', **kwargs):
"""
given a list of group ids, update each of its severity to given
severity value.
@type group_ids: basestring/list
@param group_ids: comma separated group ids or list of group ids
@type severity: basestring
@param severity: one of the many supported severity values
@type split_by: basestring
@param split_by: if `group_ids` is a string, what are the group ids split
by? defaults to `,`
@type kwargs: dict
@param kwargs: other time specific params like `earliest_time` and
`latest_time` to locate your event. Pass nothing if you dont know these
values.
@rtype: list
@return: updated notable event groups
"""
if isinstance(group_ids, itsi_py3.string_type):
if not group_ids.strip():
raise ValueError('Expecting `group_ids` to contain some value. Received: %s' % group_ids)
group_ids = group_ids.split(split_by)
if not isinstance(group_ids, list):
raise TypeError('Expecting `group_ids` to be of type basestring list. Received: {}. Type: {}'
.format(group_ids, type(group_ids).__name__))
if not group_ids or not severity.strip():
raise ValueError('Expecting non-empty list of `group_ids`. and valid severity string')
# prepare data
data = []
for i in group_ids:
data.append({'severity': severity, 'group_id': i})
kwargs['is_partial_update'] = True
self.logger.debug('Updating keys: `%s` with: %s. kwargs: %s', group_ids, data, kwargs)
objects = self.group.update_bulk(group_ids, data, kwargs)
return objects
def update_status(self, group_ids, status, split_by=',', **kwargs):
"""
given list of group ids, update each of its status to given
value.
@type group_ids: basestring/list
@param group_ids: comma separated group ids or list of group ids
@type status: basestring
@param status: value of the new status
@type split_by: basestring
@param split_by: if `group_ids` is a string, what are the group ids split
by? defaults to `,`
@type kwargs: dict
@param kwargs: other time specific params like `earliest_time` and
`latest_time` to locate your event. Pass nothing if you dont know these
values.
@rtype: list
@return: updated notable event groups
"""
if isinstance(group_ids, itsi_py3.string_type):
if not group_ids.strip():
raise ValueError('Expecting `group_ids` to contain some value. Received: {}'.format(group_ids))
group_ids = group_ids.split(split_by)
if not isinstance(group_ids, list):
raise TypeError('Expecting `group_ids` to be of type basestring list. Received: {}. Type: {}'.
format(group_ids, type(group_ids).__name__))
if not group_ids or not status.strip():
raise ValueError('Expecting non-empty list of `group_ids`. and valid status string')
# prepare data
data = []
for i in group_ids:
data.append({'status': status, 'group_id': i})
kwargs['is_partial_update'] = True
self.logger.debug('Updating keys: `%s` with: %s. kwargs: %s', group_ids, data, kwargs)
objects = self.group.update_bulk(group_ids, data, kwargs)
return objects
def update_owner(self, group_ids, owner, split_by=',', **kwargs):
"""given list of group ids, update each of its owner to given
value.
@type group_ids: basestring/list
@param group_ids: comma separated group ids or list of group ids
@type owner: basestring
@param owner: value of the new owner
@type split_by: basestring
@param split_by: if `group_ids` is a string, what are the group ids split
by? defaults to `,`
@type kwargs: dict
@param kwargs: other time specific params like `earliest_time` and
`latest_time` to locate your event. Pass nothing if you dont know these
values.
@rtype: list
@return: updated notable event groups
"""
if isinstance(group_ids, itsi_py3.string_type):
if not group_ids.strip():
raise ValueError('Expecting `group_ids` to contain some value. Received: %s' % group_ids)
group_ids = group_ids.split(split_by)
if not isinstance(group_ids, list):
raise TypeError('Expecting `group_ids` to be of type basestring list. Received: %s. Type: %s'
% (group_ids, type(group_ids).__name__))
if not group_ids or not owner.strip():
raise ValueError('Expecting non-empty list of `group_ids`. and valid owner string')
# prepare data
data = []
for i in group_ids:
data.append({'owner': owner, 'group_id': i})
kwargs['is_partial_update'] = True
self.logger.debug('Updating keys: `%s` with: %s. kwargs: %s', group_ids, data, kwargs)
objects = self.group.update_bulk(group_ids, data, kwargs)
return objects
def get(self, group_id):
"""
Get the EventGroup Object
@type group_id: string
@param group_id: id of the group where add_drilldown to be operated on
"""
return self.group.get(group_id)