if gov_values.op == 'delete_notification': return True, '' notification = message['notification'] resource_id = notification.origin if notification.origin_type == RT.Org: org = self.clients.resource_registry.read(resource_id) if (has_org_role(gov_values.actor_roles, org.org_governance_name, [ORG_MEMBER_ROLE])): return True, '' else: orgs,_ = self.clients.resource_registry.find_subjects(subject_type=RT.Org, predicate=PRED.hasResource, object=resource_id, id_only=False) for org in orgs: if (has_org_role(gov_values.actor_roles, org.org_governance_name, [ORG_MEMBER_ROLE])): return True, '' return False, '%s(%s) has been denied since the user is not a member in any org to which the resource id %s belongs ' % (process.name, gov_values.op, resource_id) def check_publish_event_policy(self, process, message, headers): try: gov_values = GovernanceHeaderValues(headers=headers, process=process, resource_id_required=False) except Inconsistent, ex: return False, ex.message if (message['event_type'] == 'ResourceIssueReportedEvent') and (has_org_role(gov_values.actor_roles, 'ION', [ORG_MEMBER_ROLE])): return True, '' return False, 'user_notification_service(publish_event) has been denied '
orgs,_ = self.clients.resource_registry.find_subjects(subject_type=RT.Org, predicate=PRED.hasResource, object=resource_id, id_only=False) for org in orgs: if (has_org_role(gov_values.actor_roles, org.org_governance_name, [ORG_MEMBER_ROLE])): return True, '' return False, '%s(%s) has been denied since the user is not a member in any org to which the resource id %s belongs ' % (process.name, gov_values.op, resource_id) def check_publish_event_policy(self, process, message, headers): try: gov_values = GovernanceHeaderValues(headers=headers, process=process, resource_id_required=False) except Inconsistent, ex: return False, ex.message if (message['event_type'] == 'ResourceIssueReportedEvent') and (has_org_role(gov_values.actor_roles, 'ION', [ORG_MEMBER_ROLE])): return True, '' resource_id = message.origin if message.origin_type == RT.Org: org = self.clients.resource_registry.read(resource_id) if (has_org_role(gov_values.actor_roles, org.org_governance_name, [ORG_MANAGER_ROLE, INSTRUMENT_OPERATOR, OBSERVATORY_OPERATOR, DATA_OPERATOR])): return True, '' else: return False, 'user does not have appropriate role in %s' % (org.org_governance_name) # Allow actor to activate/deactivate deployment in an org where the actor has the appropriate role orgs,_ = self.clients.resource_registry.find_subjects(subject_type=RT.Org, predicate=PRED.hasResource, object=resource_id, id_only=False) for org in orgs: log.error('org: ' +str(org))
class ResourceRegistryService(BaseResourceRegistryService): """ Service that manages resources instances and all cross-cutting concerns of system resources. Uses a datastore instance for resource object persistence. """ def on_init(self): # Use the wrapper to adapt the container resource registry to the service interface. # It also provides mapping from process context actor_id to function arguments. self.resource_registry = ResourceRegistryServiceWrapper(self.container.resource_registry, self) # ------------------------------------------------------------------------- # Resource CRUDs and mults def create(self, object=None): return self.resource_registry.create(object=object) def read(self, object_id='', rev_id=''): return self.resource_registry.read(object_id=object_id, rev_id=rev_id) def read_mult(self, object_ids=None): return self.resource_registry.read_mult(object_ids) def update(self, object=None): return self.resource_registry.update(object=object) def delete(self, object_id=''): return self.resource_registry.delete(object_id=object_id) # ------------------------------------------------------------------------- # Resource LCS change def retire(self, resource_id=''): return self.resource_registry.retire(resource_id=resource_id) def lcs_delete(self, resource_id=''): return self.resource_registry.lcs_delete(resource_id=resource_id) def execute_lifecycle_transition(self, resource_id='', transition_event=''): return self.resource_registry.execute_lifecycle_transition(resource_id=resource_id, transition_event=transition_event) def set_lifecycle_state(self, resource_id='', target_lcstate=''): return self.resource_registry.set_lifecycle_state(resource_id=resource_id, target_lcstate=target_lcstate) # ------------------------------------------------------------------------- # Attachments def create_attachment(self, resource_id='', attachment=None): return self.resource_registry.create_attachment(resource_id=resource_id, attachment=attachment) def read_attachment(self, attachment_id='', include_content=False): return self.resource_registry.read_attachment(attachment_id=attachment_id, include_content=include_content) def delete_attachment(self, attachment_id=''): return self.resource_registry.delete_attachment(attachment_id=attachment_id) def find_attachments(self, resource_id='', keyword='', limit=0, descending=False, include_content=False, id_only=True): return self.resource_registry.find_attachments( resource_id=resource_id, keyword='', limit=limit, descending=descending, include_content=include_content, id_only=id_only) # ------------------------------------------------------------------------- # Association CRUDs and finds def create_association(self, subject=None, predicate=None, object=None, assoc_type=None): return self.resource_registry.create_association(subject=subject, predicate=predicate, object=object, assoc_type=assoc_type) def create_association_mult(self, assoc_list=None): return self.resource_registry.create_association_mult(assoc_list=assoc_list) def delete_association(self, association=''): return self.resource_registry.delete_association(association=association) def find_associations(self, subject='', predicate='', object='', assoc_type=None, id_only=False, anyside='', limit=0, skip=0, descending=False): return self.resource_registry.find_associations(subject=subject, predicate=predicate, object=object, assoc_type=assoc_type, id_only=id_only, anyside=anyside, limit=limit, skip=skip, descending=descending) def get_association(self, subject="", predicate="", object="", assoc_type=None, id_only=False): return self.resource_registry.get_association(subject=subject, predicate=predicate, object=object, assoc_type=assoc_type, id_only=id_only) # ------------------------------------------------------------------------- # Resource finds def read_object(self, subject="", predicate="", object_type="", assoc="", id_only=False): return self.resource_registry.read_object(subject=subject, predicate=predicate, object_type=object_type, assoc=assoc, id_only=id_only) def find_objects(self, subject="", predicate="", object_type="", id_only=False, limit=0, skip=0, descending=False): return self.resource_registry.find_objects(subject=subject, predicate=predicate, object_type=object_type, id_only=id_only, limit=limit, skip=skip, descending=descending) def find_objects_mult(self, subjects=[], id_only=False, predicate=""): return self.resource_registry.find_objects_mult(subjects=subjects, id_only=id_only, predicate=predicate) def read_subject(self, subject_type="", predicate="", object="", assoc="", id_only=False): return self.resource_registry.read_subject(subject_type=subject_type, predicate=predicate, object=object, assoc=assoc, id_only=id_only) def find_subjects(self, subject_type="", predicate="", object="", id_only=False, limit=0, skip=0, descending=False): return self.resource_registry.find_subjects(subject_type=subject_type, predicate=predicate, object=object, id_only=id_only, limit=limit, skip=skip, descending=descending) def find_subjects_mult(self, objects=None, id_only=False, predicate=""): return self.resource_registry.find_subjects_mult(objects=objects, id_only=id_only, predicate=predicate) def find_resources(self, restype="", lcstate="", name="", id_only=False): return self.resource_registry.find_resources(restype=restype, lcstate=lcstate, name=name, id_only=id_only) def find_resources_ext(self, restype='', lcstate='', name='', keyword='', nested_type='', attr_name='', attr_value='', alt_id='', alt_id_ns='', limit=0, skip=0, descending=False, id_only=False, query=None): return self.resource_registry.find_resources_ext(restype=restype, lcstate=lcstate, name=name, keyword=keyword, nested_type=nested_type, attr_name=attr_name, attr_value=attr_value, alt_id=alt_id, alt_id_ns=alt_id_ns, limit=limit, skip=skip, descending=descending, id_only=id_only, query=query) # ------------------------------------------------------------------------- # Extended resource framework def get_resource_extension(self, resource_id='', resource_extension='', ext_associations=None, ext_exclude=None, optional_args=None): """Returns any ExtendedResource object containing additional related information derived from associations """ # Ensure that it is not a NoneType optional_args = dict() if optional_args is None else optional_args return self.resource_registry.get_resource_extension(resource_extension=resource_extension, resource_id=resource_id, computed_resource_type=OT.ComputedAttributes, ext_associations=ext_associations, ext_exclude=ext_exclude, **optional_args) def prepare_resource_support(self, resource_type='', resource_id=''): """ Returns the object containing the data to create/update a resource """ return self.resource_registry.prepare_resource_support(resource_type=resource_type, resource_id=resource_id) # ------------------------------------------------------------------------- # Governance functions def check_attachment_policy(self, process, message, headers): try: gov_values = GovernanceHeaderValues(headers=headers, process=process, resource_id_required=False) except Inconsistent, ex: return False, ex.message resource_id = message.resource_id resource = self.resource_registry.read(resource_id) # Allow attachment to an org if resource.type_ == 'Org': if (has_org_role(gov_values.actor_roles, resource.org_governance_name, [MODERATOR_ROLE, OPERATOR_ROLE])): return True, '' # Allow actor to add attachment to his own UserInfo elif resource.type_ == 'UserInfo': actor_identity,_ = self.resource_registry.find_subjects(subject_type=RT.ActorIdentity, predicate=PRED.hasInfo, object=resource_id, id_only=False) if actor_identity[0]._id == headers[MSG_HEADER_ACTOR]: return True, '' # Allow actor to add attachment to any resource in an org where the actor has appropriate role else: orgs,_ = self.resource_registry.find_subjects(subject_type=RT.Org, predicate=PRED.hasResource, object=resource_id, id_only=False) for org in orgs: if (has_org_role(gov_values.actor_roles, org.org_governance_name, [MODERATOR_ROLE, OPERATOR_ROLE])): return True, '' return False, '%s(%s) has been denied since the user is not a member in any org to which the resource id %s belongs ' % (process.name, gov_values.op, resource_id)
return True, '' return False, '%s(%s) has been denied since the user is not a member in any org to which the resource id %s belongs ' % (process.name, gov_values.op, resource_id) def check_edit_policy(self, process, message, headers): try: gov_values = GovernanceHeaderValues(headers=headers, process=process, resource_id_required=True) except Inconsistent, ex: return False, ex.message resource_id = gov_values.resource_id resource = self.resource_registry.read(resource_id) # Allow edit to an org if resource.type_ == 'Org': if (has_org_role(gov_values.actor_roles, resource.org_governance_name, [ORG_MANAGER_ROLE, INSTRUMENT_OPERATOR, OBSERVATORY_OPERATOR, DATA_OPERATOR])): return True, '' # Allow edit to add attachment to his own UserInfo elif resource.type_ == 'UserInfo': actor_identity,_ = self.resource_registry.find_subjects(subject_type=RT.ActorIdentity, predicate=PRED.hasInfo, object=resource_id, id_only=False) if actor_identity[0]._id == headers['ion-actor-id']: return True, '' # Allow actor to add attachment to any resource in an org where the actor has appropriate role else: orgs,_ = self.resource_registry.find_subjects(subject_type=RT.Org, predicate=PRED.hasResource, object=resource_id, id_only=False) for org in orgs: if (has_org_role(gov_values.actor_roles, org.org_governance_name, [ORG_MANAGER_ROLE, INSTRUMENT_OPERATOR, OBSERVATORY_OPERATOR, DATA_OPERATOR])): return True, ''
return True, '' return False, '%s(%s) has been denied since the user is not a member in any org to which the resource id %s belongs ' % (process.name, gov_values.op, resource_id) def check_edit_policy(self, process, message, headers): try: gov_values = GovernanceHeaderValues(headers=headers, process=process, resource_id_required=True) except Inconsistent, ex: return False, ex.message resource_id = gov_values.resource_id resource = self.resource_registry.read(resource_id) # Allow edit to an org if resource.type_ == 'Org': if (has_org_role(gov_values.actor_roles, resource.org_governance_name, [MODERATOR_ROLE, OPERATOR_ROLE])): return True, '' # Allow edit to add attachment to his own UserInfo elif resource.type_ == 'UserInfo': actor_identity,_ = self.resource_registry.find_subjects(subject_type=RT.ActorIdentity, predicate=PRED.hasInfo, object=resource_id, id_only=False) if actor_identity[0]._id == headers[MSG_HEADER_ACTOR]: return True, '' # Allow actor to add attachment to any resource in an org where the actor has appropriate role else: orgs,_ = self.resource_registry.find_subjects(subject_type=RT.Org, predicate=PRED.hasResource, object=resource_id, id_only=False) for org in orgs: if (has_org_role(gov_values.actor_roles, org.org_governance_name, [MODERATOR_ROLE, OPERATOR_ROLE])): return True, ''
class UserNotificationService(BaseUserNotificationService): """ A service that provides users with an API for CRUD methods for notifications. """ def __init__(self, *args, **kwargs): self._schedule_ids = [] BaseUserNotificationService.__init__(self, *args, **kwargs) def on_start(self): self.ION_NOTIFICATION_EMAIL_ADDRESS = CFG.get_safe( 'server.smtp.sender') # Create an event processor self.event_processor = EmailEventProcessor() # Dictionaries that maintain information asetting_up_smtp_clientbout users and their subscribed notifications self.user_info = {} # The reverse_user_info is calculated from the user_info dictionary self.reverse_user_info = {} # Get the clients # @TODO: Why are these not dependencies in the service YML??? self.discovery = DiscoveryServiceClient() self.process_dispatcher = ProcessDispatcherServiceClient() self.event_publisher = EventPublisher() self.datastore = self.container.datastore_manager.get_datastore( 'events') self.start_time = get_ion_ts() #------------------------------------------------------------------------------------ # Create an event subscriber for Reload User Info events #------------------------------------------------------------------------------------ def reload_user_info(event_msg, headers): """ Callback method for the subscriber to ReloadUserInfoEvent """ notification_id = event_msg.notification_id log.debug( "(UNS instance received a ReloadNotificationEvent. The relevant notification_id is %s" % notification_id) try: self.user_info = self.load_user_info() except NotFound: log.warning("ElasticSearch has not yet loaded the user_index.") self.reverse_user_info = calculate_reverse_user_info( self.user_info) log.debug("(UNS instance) After a reload, the user_info: %s" % self.user_info) log.debug("(UNS instance) The recalculated reverse_user_info: %s" % self.reverse_user_info) # the subscriber for the ReloadUSerInfoEvent self.reload_user_info_subscriber = EventSubscriber( event_type=OT.ReloadUserInfoEvent, origin='UserNotificationService', callback=reload_user_info) self.add_endpoint(self.reload_user_info_subscriber) def on_quit(self): """ Handles stop/terminate. Cleans up subscribers spawned here, terminates any scheduled tasks to the scheduler. """ for sid in self._schedule_ids: try: self.clients.scheduler.cancel_timer(sid) except IonException as ex: log.info( "Ignoring exception while cancelling schedule id (%s): %s: %s", sid, ex.__class__.__name__, ex) super(UserNotificationService, self).on_quit() def set_process_batch_key(self, process_batch_key=''): """ This method allows an operator to set the process_batch_key, a string. Once this method is used by the operator, the UNS will start listening for timer events published by the scheduler with origin = process_batch_key. @param process_batch_key str """ def process(event_msg, headers): self.end_time = get_ion_ts() # run the process_batch() method self.process_batch(start_time=self.start_time, end_time=self.end_time) self.start_time = self.end_time # the subscriber for the batch processing # To trigger the batch notification, have the scheduler create a timer with event_origin = process_batch_key self.batch_processing_subscriber = EventSubscriber( event_type=OT.TimerEvent, origin=process_batch_key, callback=process) self.add_endpoint(self.batch_processing_subscriber) def create_notification(self, notification=None, user_id=''): """ Persists the provided NotificationRequest object for the specified Origin id. Associate the Notification resource with the user_id string. returned id is the internal id by which NotificationRequest will be identified in the data store. @param notification NotificationRequest @param user_id str @retval notification_id str @throws BadRequest if object passed has _id or _rev attribute """ if not user_id: raise BadRequest("User id not provided.") log.debug( "Create notification called for user_id: %s, and notification: %s", user_id, notification) #--------------------------------------------------------------------------------------------------- # Persist Notification object as a resource if it has already not been persisted #--------------------------------------------------------------------------------------------------- notification_id = None # if the notification has already been registered, simply use the old id existing_user_notifications = self.get_user_notifications( user_info_id=user_id) if existing_user_notifications: notification_id = self._notification_in_notifications( notification, existing_user_notifications) # since the notification has not been registered yet, register it and get the id temporal_bounds = TemporalBounds() temporal_bounds.start_datetime = get_ion_ts() temporal_bounds.end_datetime = '' if not notification_id: notification.temporal_bounds = temporal_bounds notification_id, rev = self.clients.resource_registry.create( notification) else: log.debug( "Notification object has already been created in resource registry before. No new id to be generated. notification_id: %s", notification_id) # Read the old notification already in the resource registry notification = self.clients.resource_registry.read(notification_id) # Update the temporal bounds of the old notification resource notification.temporal_bounds = temporal_bounds # Update the notification in the resource registry self.clients.resource_registry.update(notification) log.debug( "The temporal bounds for this resubscribed notification object with id: %s, is: %s", notification._id, notification.temporal_bounds) # Link the user and the notification with a hasNotification association assocs = self.clients.resource_registry.find_associations( subject=user_id, predicate=PRED.hasNotification, object=notification_id, id_only=True) if assocs: log.debug( "Got an already existing association: %s, between user_id: %s, and notification_id: %s", assocs, user_id, notification_id) return notification_id else: log.debug( "Creating association between user_id: %s, and notification_id: %s", user_id, notification_id) self.clients.resource_registry.create_association( user_id, PRED.hasNotification, notification_id) # read the registered notification request object because this has an _id and is more useful notification = self.clients.resource_registry.read(notification_id) #------------------------------------------------------------------------------------------------------------------- # Generate an event that can be picked by a notification worker so that it can update its user_info dictionary #------------------------------------------------------------------------------------------------------------------- log.debug( "(create notification) Publishing ReloadUserInfoEvent for notification_id: %s", notification_id) self.event_publisher.publish_event( event_type=OT.ReloadUserInfoEvent, origin="UserNotificationService", description="A notification has been created.", notification_id=notification_id) return notification_id def update_notification(self, notification=None, user_id=''): """Updates the provided NotificationRequest object. Throws NotFound exception if an existing version of NotificationRequest is not found. Throws Conflict if the provided NotificationRequest object is not based on the latest persisted version of the object. @param notification NotificationRequest @throws BadRequest if object does not have _id or _rev attribute @throws NotFound object with specified id does not exist @throws Conflict object not based on latest persisted object version """ raise NotImplementedError( "This method needs to be worked out in terms of implementation") # #------------------------------------------------------------------------------------------------------------------- # # Get the old notification # #------------------------------------------------------------------------------------------------------------------- # # old_notification = self.clients.resource_registry.read(notification._id) # # #------------------------------------------------------------------------------------------------------------------- # # Update the notification in the notifications dict # #------------------------------------------------------------------------------------------------------------------- # # # self._update_notification_in_notifications_dict(new_notification=notification, # notifications=self.notifications) # #------------------------------------------------------------------------------------------------------------------- # # Update the notification in the registry # #------------------------------------------------------------------------------------------------------------------- # ''' # Since one user should not be able to update the notification request resource without the knowledge of other users # who have subscribed to the same notification request, we do not update the resource in the resource registry # ''' # ## self.clients.resource_registry.update(notification) # # #------------------------------------------------------------------------------------------------------------------- # # reading up the notification object to make sure we have the newly registered notification request object # #------------------------------------------------------------------------------------------------------------------- # # notification_id = notification._id # notification = self.clients.resource_registry.read(notification_id) # # #------------------------------------------------------------------------------------ # # Update the UserInfo object # #------------------------------------------------------------------------------------ # # user = self.update_user_info_object(user_id, notification) # # #------------------------------------------------------------------------------------------------------------------- # # Generate an event that can be picked by notification workers so that they can update their user_info dictionary # #------------------------------------------------------------------------------------------------------------------- # log.info("(update notification) Publishing ReloadUserInfoEvent for updated notification") # # self.event_publisher.publish_event( event_type= "ReloadUserInfoEvent", # origin="UserNotificationService", # description= "A notification has been updated.", # notification_id = notification_id # ) def read_notification(self, notification_id=''): """Returns the NotificationRequest object for the specified notification id. Throws exception if id does not match any persisted NotificationRequest objects. @param notification_id str @retval notification NotificationRequest @throws NotFound object with specified id does not exist """ notification = self.clients.resource_registry.read(notification_id) return notification def delete_notification(self, notification_id=''): """For now, permanently deletes NotificationRequest object with the specified id. Throws exception if id does not match any persisted NotificationRequest. @param notification_id str @throws NotFound object with specified id does not exist """ #------------------------------------------------------------------------------------------------------------------- # Stop the event subscriber for the notification #------------------------------------------------------------------------------------------------------------------- notification_request = self.clients.resource_registry.read( notification_id) #------------------------------------------------------------------------------------------------------------------- # Update the resource registry #------------------------------------------------------------------------------------------------------------------- notification_request.temporal_bounds.end_datetime = get_ion_ts() self.clients.resource_registry.update(notification_request) #------------------------------------------------------------------------------------------------------------------- # Find users who are interested in the notification and update the notification in the list maintained by the UserInfo object #------------------------------------------------------------------------------------------------------------------- # user_ids, _ = self.clients.resource_registry.find_subjects(RT.UserInfo, PRED.hasNotification, notification_id, True) # # for user_id in user_ids: # self.update_user_info_object(user_id, notification_request) #------------------------------------------------------------------------------------------------------------------- # Generate an event that can be picked by a notification worker so that it can update its user_info dictionary #------------------------------------------------------------------------------------------------------------------- log.info( "(delete notification) Publishing ReloadUserInfoEvent for notification_id: %s", notification_id) self.event_publisher.publish_event( event_type=OT.ReloadUserInfoEvent, origin="UserNotificationService", description="A notification has been deleted.", notification_id=notification_id) # def delete_notification_from_user_info(self, notification_id): # """ # Helper method to delete the notification from the user_info dictionary # # @param notification_id str # """ # # user_ids, assocs = self.clients.resource_registry.find_subjects(object=notification_id, predicate=PRED.hasNotification, id_only=True) # # for assoc in assocs: # self.clients.resource_registry.delete_association(assoc) # # for user_id in user_ids: # # value = self.user_info[user_id] # # for notif in value['notifications']: # if notification_id == notif._id: # # remove the notification # value['notifications'].remove(notif) # # self.reverse_user_info = calculate_reverse_user_info(self.user_info) def find_events(self, origin='', type='', min_datetime=0, max_datetime=0, limit=-1, descending=False): """ This method leverages couchdb view and simple filters. It does not use elastic search. Returns a list of events that match the specified search criteria. Will throw a not NotFound exception if no events exist for the given parameters. @param origin str @param min_datetime int seconds @param max_datetime int seconds @param limit int (integer limiting the number of results (0 means unlimited)) @param descending boolean (if True, reverse order (of production time) is applied, e.g. most recent first) @retval event_list [] @throws NotFound object with specified parameters does not exist @throws NotFound object with specified parameters does not exist """ event_tuples = [] try: event_tuples = self.container.event_repository.find_events( event_type=type, origin=origin, start_ts=min_datetime, end_ts=max_datetime, limit=limit, descending=descending) except Exception as exc: log.warning( "The UNS find_events operation for event origin = %s and type = %s failed. Error message = %s", origin, type, exc.message) events = [item[2] for item in event_tuples] log.debug("(find_events) UNS found the following relevant events: %s", events) return events #todo Uses Elastic Search. Later extend this to a larger search criteria def find_events_extended(self, origin='', type='', min_time=0, max_time=0, limit=-1, descending=False): """Uses Elastic Search. Returns a list of events that match the specified search criteria. Will throw a not NotFound exception if no events exist for the given parameters. @param origin str @param type str @param min_time int seconds @param max_time int seconds @param limit int (integer limiting the number of results (0 means unlimited)) @param descending boolean (if True, reverse order (of production time) is applied, e.g. most recent first) @retval event_list [] @throws NotFound object with specified parameters does not exist @throws NotFound object with specified parameters does not exist """ query = [] if min_time and max_time: query.append( "SEARCH 'ts_created' VALUES FROM %s TO %s FROM 'events_index'" % (min_time, max_time)) if origin: query.append('search "origin" is "%s" from "events_index"' % origin) if type: query.append('search "type_" is "%s" from "events_index"' % type) search_string = ' and '.join(query) # get the list of ids corresponding to the events ret_vals = self.discovery.parse(search_string) if len(query) > 1: events = self.datastore.read_mult(ret_vals) else: events = [i['_source'] for i in ret_vals] log.debug( "(find_events_extended) Discovery search returned the following event ids: %s", ret_vals) log.debug( "(find_events_extended) UNS found the following relevant events: %s", events) if limit > 0: return events[:limit] #todo implement time ordering: ascending or descending return events def publish_event_object(self, event=None): """ This service operation would publish the given event from an event object. @param event !Event @retval event !Event """ event = self.event_publisher.publish_event_object(event_object=event) log.info( "The publish_event_object(event) method of UNS was used to publish the event: %s", event) return event def publish_event(self, event_type='', origin='', origin_type='', sub_type='', description='', event_attrs=None): """ This service operation assembles a new Event object based on event_type (e.g. via the pyon Event publisher) with optional additional attributes from a event_attrs dict of arbitrary attributes. @param event_type str @param origin str @param origin_type str @param sub_type str @param description str @param event_attrs dict @retval event !Event """ event_attrs = event_attrs or {} event = self.event_publisher.publish_event(event_type=event_type, origin=origin, origin_type=origin_type, sub_type=sub_type, description=description, **event_attrs) log.info( "The publish_event() method of UNS was used to publish an event: %s", event) return event def get_recent_events(self, resource_id='', limit=100): """ Get recent events for use in extended resource computed attribute @param resource_id str @param limit int @retval ComputedListValue with value list of 4-tuple with Event objects """ now = get_ion_ts() events = self.find_events(origin=resource_id, limit=limit, max_datetime=now, descending=True) ret = IonObject(OT.ComputedEventListValue) if events: ret.value = events ret.computed_list = [ get_event_computed_attributes(event) for event in events ] ret.status = ComputedValueAvailability.PROVIDED else: ret.status = ComputedValueAvailability.NOTAVAILABLE return ret def get_user_notifications(self, user_info_id=''): """ Get the notification request objects that are subscribed to by the user @param user_info_id str @retval notifications list of NotificationRequest objects """ notifications = [] user_notif_req_objs, _ = self.clients.resource_registry.find_objects( subject=user_info_id, predicate=PRED.hasNotification, object_type=RT.NotificationRequest, id_only=False) log.debug("Got %s notifications, for the user: %s", len(user_notif_req_objs), user_info_id) for notif in user_notif_req_objs: # do not include notifications that have expired if notif.temporal_bounds.end_datetime == '': notifications.append(notif) return notifications def create_worker(self, number_of_workers=1): """ Creates notification workers @param number_of_workers int @retval pids list """ pids = [] for n in xrange(number_of_workers): process_definition = ProcessDefinition( name='notification_worker_%s' % n) process_definition.executable = { 'module': 'ion.processes.data.transforms.notification_worker', 'class': 'NotificationWorker' } process_definition_id = self.process_dispatcher.create_process_definition( process_definition=process_definition) # ------------------------------------------------------------------------------------ # Process Spawning # ------------------------------------------------------------------------------------ pid2 = self.process_dispatcher.create_process( process_definition_id) #@todo put in a configuration configuration = {} configuration['process'] = dict({ 'name': 'notification_worker_%s' % n, 'type': 'simple', 'queue_name': 'notification_worker_queue' }) pid = self.process_dispatcher.schedule_process( process_definition_id, configuration=configuration, process_id=pid2) pids.append(pid) return pids def process_batch(self, start_time='', end_time=''): """ This method is launched when an process_batch event is received. The user info dictionary maintained by the User Notification Service is used to query the event repository for all events for a particular user that have occurred in a provided time interval, and then an email is sent to the user containing the digest of all the events. @param start_time int milliseconds @param end_time int milliseconds """ self.smtp_client = setting_up_smtp_client() if end_time <= start_time: return for user_id, value in self.user_info.iteritems(): notifications = self.get_user_notifications(user_info_id=user_id) notifications_disabled = value['notifications_disabled'] notifications_daily_digest = value['notifications_daily_digest'] # Ignore users who do NOT want batch notifications or who have disabled the delivery switch # However, if notification preferences have not been set for the user, use the default mechanism and do not bother if notifications_disabled or not notifications_daily_digest: continue events_for_message = [] search_time = "SEARCH 'ts_created' VALUES FROM %s TO %s FROM 'events_index'" % ( start_time, end_time) for notification in notifications: # If the notification request has expired, then do not use it in the search if notification.temporal_bounds.end_datetime: continue if CFG_ELASTIC_SEARCH: if notification.origin: search_origin = 'search "origin" is "%s" from "events_index"' % notification.origin else: search_origin = 'search "origin" is "*" from "events_index"' if notification.origin_type: search_origin_type = 'search "origin_type" is "%s" from "events_index"' % notification.origin_type else: search_origin_type = 'search "origin_type" is "*" from "events_index"' if notification.event_type: search_event_type = 'search "type_" is "%s" from "events_index"' % notification.event_type else: search_event_type = 'search "type_" is "*" from "events_index"' search_string = search_time + ' and ' + search_origin + ' and ' + search_origin_type + ' and ' + search_event_type # get the list of ids corresponding to the events log.debug('process_batch search_string: %s', search_string) ret_vals = self.discovery.parse(search_string) events_for_message.extend( self.datastore.read_mult(ret_vals)) else: # Adding a branch event_tuples = self.container.event_repository.find_events( origin=notification.origin, event_type=notification.event_type, start_ts=start_time, end_ts=end_time) events = [item[2] for item in event_tuples] events_for_message.extend(events) log.debug("Found following events of interest to user, %s: %s", user_id, events_for_message) # send a notification email to each user using a _send_email() method if events_for_message: self.format_and_send_email( events_for_message=events_for_message, user_id=user_id, smtp_client=self.smtp_client) self.smtp_client.quit() def format_and_send_email(self, events_for_message=None, user_id=None, smtp_client=None): """ Format the message for a particular user containing information about the events he is to be notified about @param events_for_message list @param user_id str """ message = str(events_for_message) log.debug( "The user, %s, will get the following events in his batch notification email: %s", user_id, message) msg = convert_events_to_email_message(events_for_message, self.clients.resource_registry) msg["Subject"] = "(SysName: " + get_sys_name() + ") ION event " msg["To"] = self.user_info[user_id]['user_contact'].email self.send_batch_email(msg, smtp_client) def send_batch_email(self, msg=None, smtp_client=None): """ Send the email @param msg MIMEText object of email message @param smtp_client object """ if msg is None: msg = {} for f in ["Subject", "To"]: if not f in msg: raise BadRequest("'%s' not in msg %s" % (f, msg)) msg_subject = msg["Subject"] msg_recipient = msg["To"] msg['From'] = self.ION_NOTIFICATION_EMAIL_ADDRESS log.debug("UNS sending batch (digest) email from %s to %s", self.ION_NOTIFICATION_EMAIL_ADDRESS, msg_recipient) smtp_sender = CFG.get_safe('server.smtp.sender') smtp_client.sendmail(smtp_sender, [msg_recipient], msg.as_string()) def update_user_info_object(self, user_id, new_notification): """ Update the UserInfo object. If the passed in parameter, od_notification, is None, it does not need to remove the old notification @param user_id str @param new_notification NotificationRequest """ #this is not necessary if notifiactions are not stored in the userinfo object raise NotImplementedError( "This method is not necessary because Notifications are not stored in the userinfo object" ) #------------------------------------------------------------------------------------ # read the user #------------------------------------------------------------------------------------ # user = self.clients.resource_registry.read(user_id) # # if not user: # raise BadRequest("No user with the provided user_id: %s" % user_id) # # for item in user.variables: # if type(item) is dict and item.has_key('name') and item['name'] == 'notifications': # for notif in item['value']: # if notif._id == new_notification._id: # log.debug("came here for updating notification") # notifications = item['value'] # notifications.remove(notif) # notifications.append(new_notification) # break # else: # log.warning('Invalid variables attribute on UserInfo instance. UserInfo: %s', user) # # # #------------------------------------------------------------------------------------ # # update the resource registry # #------------------------------------------------------------------------------------ # # log.debug("user.variables::: %s", user.variables) # # self.clients.resource_registry.update(user) # # return user def get_subscriptions(self, resource_id='', user_id='', include_nonactive=False): """ @param resource_id a resource id (or other origin) that is the origin of events for notifications @param user_id a UserInfo ID that owns the NotificationRequest @param include_nonactive if False, filter to active NotificationRequest only Return all NotificationRequest resources where origin is given resource_id. """ notif_reqs, _ = self.clients.resource_registry.find_resources_ext( restype=RT.NotificationRequest, attr_name="origin", attr_value=resource_id, id_only=False) log.debug( "Got %s active and past NotificationRequests for resource/origin %s", len(notif_reqs), resource_id) if not include_nonactive: notif_reqs = [ nr for nr in notif_reqs if nr.temporal_bounds.end_datetime == '' ] log.debug("Filtered to %s active NotificationRequests", len(notif_reqs)) if user_id: # Get all NotificationRequests (ID) that are associated to given UserInfo_id user_notif_req_ids, _ = self.clients.resource_registry.find_objects( subject=user_id, predicate=PRED.hasNotification, object_type=RT.NotificationRequest, id_only=True) notif_reqs = [ nr for nr in notif_reqs if nr._id in user_notif_req_ids ] log.debug( "Filtered to %s NotificationRequests associated to user %s", len(notif_reqs), user_id) return notif_reqs def get_subscriptions_attribute(self, resource_id='', user_id='', include_nonactive=False): retval = self.get_subscriptions(resource_id=resource_id, user_id=user_id, include_nonactive=include_nonactive) container = ComputedListValue(value=retval) return container def _notification_in_notifications(self, notification=None, notifications=None): for notif in notifications: if notif.name == notification.name and \ notif.origin == notification.origin and \ notif.origin_type == notification.origin_type and \ notif.event_type == notification.event_type: return notif._id return None def load_user_info(self): """ Method to load the user info dictionary used by the notification workers and the UNS @retval user_info dict """ users, _ = self.clients.resource_registry.find_resources( restype=RT.UserInfo) user_info = {} if not users: return {} for user in users: notifications = [] notifications_disabled = False notifications_daily_digest = False #retrieve all the active notifications assoc to this user notifications = self.get_user_notifications(user_info_id=user) log.debug('load_user_info notifications: %s', notifications) for variable in user.variables: if type(variable) is dict and variable.has_key('name'): if variable['name'] == 'notifications_daily_digest': notifications_daily_digest = variable['value'] if variable['name'] == 'notifications_disabled': notifications_disabled = variable['value'] else: log.warning( 'Invalid variables attribute on UserInfo instance. UserInfo: %s', user) user_info[user._id] = { 'user_contact': user.contact, 'notifications': notifications, 'notifications_daily_digest': notifications_daily_digest, 'notifications_disabled': notifications_disabled } return user_info ## ## ## GOVERNANCE FUNCTIONS ## ## def check_subscription_policy(self, process, message, headers): try: gov_values = GovernanceHeaderValues(headers=headers, process=process, resource_id_required=False) except Inconsistent, ex: return False, ex.message if gov_values.op == 'delete_notification': return True, '' notification = message['notification'] resource_id = notification.origin if notification.origin_type == RT.Org: org = self.clients.resource_registry.read(resource_id) if (has_org_role(gov_values.actor_roles, org.org_governance_name, [ORG_MEMBER_ROLE])): return True, '' else: orgs, _ = self.clients.resource_registry.find_subjects( subject_type=RT.Org, predicate=PRED.hasResource, object=resource_id, id_only=False) for org in orgs: if (has_org_role(gov_values.actor_roles, org.org_governance_name, [ORG_MEMBER_ROLE])): return True, '' return False, '%s(%s) has been denied since the user is not a member in any org to which the resource id %s belongs ' % ( process.name, gov_values.op, resource_id)
else: orgs, _ = self.clients.resource_registry.find_subjects( subject_type=RT.Org, predicate=PRED.hasResource, object=resource_id, id_only=False) for org in orgs: if (has_org_role(gov_values.actor_roles, org.org_governance_name, [ORG_MEMBER_ROLE])): return True, '' return False, '%s(%s) has been denied since the user is not a member in any org to which the resource id %s belongs ' % ( process.name, gov_values.op, resource_id) def check_publish_event_policy(self, process, message, headers): try: gov_values = GovernanceHeaderValues(headers=headers, process=process, resource_id_required=False) except Inconsistent, ex: return False, ex.message if (message['event_type'] == 'ResourceIssueReportedEvent') and (has_org_role( gov_values.actor_roles, 'ION', [ORG_MEMBER_ROLE])): return True, '' return False, 'user_notification_service(publish_event) has been denied '
class ResourceRegistryService(BaseResourceRegistryService): """ Service that manages resources instances and all cross-cutting concerns of system resources. Uses a datastore instance for resource object persistence. """ def on_init(self): self.resource_registry = ResourceRegistryServiceWrapper(self.container.resource_registry, self) # For easier interactive debugging self.dss = None self.ds = self.resource_registry.rr_store try: self.dss = self.resource_registry.rr_store.server[self.resource_registry.rr_store.datastore_name] except Exception: pass @mask_couch_error def create(self, object=None): return self.resource_registry.create(object=object) @mask_couch_error def read(self, object_id='', rev_id=''): return self.resource_registry.read(object_id=object_id, rev_id=rev_id) @mask_couch_error def update(self, object=None): return self.resource_registry.update(object=object) @mask_couch_error def delete(self, object_id=''): return self.resource_registry.delete(object_id=object_id) @mask_couch_error def retire(self, resource_id=''): return self.resource_registry.retire(resource_id=resource_id) @mask_couch_error def execute_lifecycle_transition(self, resource_id='', transition_event=''): return self.resource_registry.execute_lifecycle_transition(resource_id=resource_id, transition_event=transition_event) @mask_couch_error def set_lifecycle_state(self, resource_id='', target_lcstate=''): return self.resource_registry.set_lifecycle_state(resource_id=resource_id, target_lcstate=target_lcstate) @mask_couch_error def create_attachment(self, resource_id='', attachment=None): return self.resource_registry.create_attachment(resource_id=resource_id, attachment=attachment) @mask_couch_error def read_attachment(self, attachment_id='', include_content=False): return self.resource_registry.read_attachment(attachment_id=attachment_id, include_content=include_content) @mask_couch_error def delete_attachment(self, attachment_id=''): return self.resource_registry.delete_attachment(attachment_id=attachment_id) @mask_couch_error def find_attachments(self, resource_id='', keyword='', limit=0, descending=False, include_content=False, id_only=True): return self.resource_registry.find_attachments( resource_id=resource_id, keyword='', limit=limit, descending=descending, include_content=include_content, id_only=id_only) @mask_couch_error def create_association(self, subject=None, predicate=None, object=None, assoc_type=None): return self.resource_registry.create_association(subject=subject, predicate=predicate, object=object, assoc_type=assoc_type) @mask_couch_error def delete_association(self, association=''): return self.resource_registry.delete_association(association=association) @mask_couch_error def read_object(self, subject="", predicate="", object_type="", assoc="", id_only=False): return self.resource_registry.read_object(subject=subject, predicate=predicate, object_type=object_type, assoc=assoc, id_only=id_only) @mask_couch_error def find_objects(self, subject="", predicate="", object_type="", id_only=False, limit=0, skip=0, descending=False): return self.resource_registry.find_objects(subject=subject, predicate=predicate, object_type=object_type, id_only=id_only, limit=limit, skip=skip, descending=descending) @mask_couch_error def read_subject(self, subject_type="", predicate="", object="", assoc="", id_only=False): return self.resource_registry.read_subject(subject_type=subject_type, predicate=predicate, object=object, assoc=assoc, id_only=id_only) @mask_couch_error def find_subjects(self, subject_type="", predicate="", object="", id_only=False, limit=0, skip=0, descending=False): return self.resource_registry.find_subjects(subject_type=subject_type, predicate=predicate, object=object, id_only=id_only, limit=limit, skip=skip, descending=descending) @mask_couch_error def find_associations(self, subject="", predicate="", object="", assoc_type=None, id_only=False, limit=0, skip=0, descending=False): return self.resource_registry.find_associations(subject=subject, predicate=predicate, object=object, assoc_type=assoc_type, id_only=id_only, limit=limit, skip=skip, descending=descending) @mask_couch_error def find_objects_mult(self, subjects=[], id_only=False): return self.resource_registry.find_objects_mult(subjects=subjects, id_only=id_only) @mask_couch_error def find_subjects_mult(self, objects=[], id_only=False): return self.resource_registry.find_subjects_mult(objects=objects, id_only=id_only) @mask_couch_error def get_association(self, subject="", predicate="", object="", assoc_type=None, id_only=False): return self.resource_registry.get_association(subject=subject, predicate=predicate, object=object, assoc_type=assoc_type, id_only=id_only) @mask_couch_error def find_resources(self, restype="", lcstate="", name="", id_only=False): return self.resource_registry.find_resources(restype=restype, lcstate=lcstate, name=name, id_only=id_only) @mask_couch_error def find_resources_ext(self, restype='', lcstate='', name='', keyword='', nested_type='', attr_name='', attr_value='', alt_id='', alt_id_ns='', limit=0, skip=0, descending=False, id_only=False): return self.resource_registry.find_resources_ext(restype=restype, lcstate=lcstate, name=name, keyword=keyword, nested_type=nested_type, attr_name=attr_name, attr_value=attr_value, alt_id=alt_id, alt_id_ns=alt_id_ns, limit=limit, skip=skip, descending=descending, id_only=id_only) @mask_couch_error def read_mult(self, object_ids=[]): return self.resource_registry.read_mult(object_ids) ############################ # # RESOURCE EXTENSION # ############################ @mask_couch_error def get_resource_extension(self, resource_id='', resource_extension='', ext_associations=None, ext_exclude=None, optional_args=None): """Returns any ExtendedResource object containing additional related information derived from associations @param resource_id str @param resource_extension str @param ext_associations dict @param ext_exclude list @param optional_args dict @retval extended_resource ExtendedResource @throws BadRequest A parameter is missing @throws NotFound An object with the specified resource_id does not exist """ #Ensure that it is not a NoneType optional_args = dict() if optional_args is None else optional_args return self.resource_registry.get_resource_extension(resource_extension=resource_extension, resource_id=resource_id, computed_resource_type=OT.ComputedAttributes, ext_associations=ext_associations, ext_exclude=ext_exclude, **optional_args) ############################ # # PREPARE RESOURCE SUPPORT # ############################ @mask_couch_error def prepare_resource_support(self, resource_type='', resource_id=''): """ Returns the object containing the data to create/update a resource """ return self.resource_registry.prepare_resource_support(resource_type=resource_type, resource_id=resource_id) ## ## ## GOVERNANCE FUNCTION ## ## def check_attachment_policy(self, process, message, headers): try: gov_values = GovernanceHeaderValues(headers=headers, process=process, resource_id_required=False) except Inconsistent, ex: return False, ex.message resource_id = message.resource_id resource = self.resource_registry.read(resource_id) # Allow attachment to an org if resource.type_ == 'Org': if (has_org_role(gov_values.actor_roles, resource.org_governance_name, [ORG_MANAGER_ROLE, INSTRUMENT_OPERATOR, OBSERVATORY_OPERATOR, DATA_OPERATOR])): return True, '' # Allow actor to add attachment to his own UserInfo elif resource.type_ == 'UserInfo': actor_identity,_ = self.resource_registry.find_subjects(subject_type=RT.ActorIdentity, predicate=PRED.hasInfo, object=resource_id, id_only=False) if actor_identity[0]._id == headers['ion-actor-id']: return True, '' # Allow actor to add attachment to any resource in an org where the actor has appropriate role else: orgs,_ = self.resource_registry.find_subjects(subject_type=RT.Org, predicate=PRED.hasResource, object=resource_id, id_only=False) for org in orgs: if (has_org_role(gov_values.actor_roles, org.org_governance_name, [ORG_MANAGER_ROLE, INSTRUMENT_OPERATOR, OBSERVATORY_OPERATOR, DATA_OPERATOR])): return True, '' return False, '%s(%s) has been denied since the user is not a member in any org to which the resource id %s belongs ' % (process.name, gov_values.op, resource_id)
def check_edit_policy(self, process, message, headers): try: gov_values = GovernanceHeaderValues(headers=headers, process=process, resource_id_required=True) except Inconsistent, ex: return False, ex.message resource_id = gov_values.resource_id resource = self.resource_registry.read(resource_id) # Allow edit to an org if resource.type_ == 'Org': if (has_org_role(gov_values.actor_roles, resource.org_governance_name, [ ORG_MANAGER_ROLE, INSTRUMENT_OPERATOR, OBSERVATORY_OPERATOR, DATA_OPERATOR ])): return True, '' # Allow edit to add attachment to his own UserInfo elif resource.type_ == 'UserInfo': actor_identity, _ = self.resource_registry.find_subjects( subject_type=RT.ActorIdentity, predicate=PRED.hasInfo, object=resource_id, id_only=False) if actor_identity[0]._id == headers['ion-actor-id']: return True, '' # Allow actor to add attachment to any resource in an org where the actor has appropriate role else: