def test_create_single_timer(self): # test creating a new timer that is one-time-only # create the timer resource # create the event listener # call scheduler to set the timer # create then cancel the timer, verify that event is not received # create the timer resource # create the event listener # call scheduler to set the timer # call scheduler to cancel the timer # wait until after expiry to verify that event is not sent self.single_timer_count = 0 event_origin = "Time of Day" sub = EventSubscriber(event_type="ResourceEvent", callback=self.single_timer_call_back, origin=event_origin) sub.start() # Time out in 3 seconds now = datetime.datetime.utcnow() + timedelta(seconds=3) times_of_day =[{'hour': str(now.hour),'minute' : str(now.minute), 'second':str(now.second) }] ss = SchedulerService() id = ss.create_time_of_day_timer(times_of_day=times_of_day, expires=time.time()+25200+60, event_origin=event_origin, event_subtype="") self.assertEqual(type(id), str) ss.cancel_timer(id) gevent.sleep(5) # Validate the event is not sent self.assertEqual(self.single_timer_count, 0)
def test_timeoffday_timer(self): # test creating a new timer that is one-time-only # create the timer resource # get the current time, set the timer to several seconds from current time # create the event listener # call scheduler to set the timer # verify that event arrival is within one/two seconds of current time ss = SchedulerService() event_origin = "Time Of Day2" self.expire_sec_1 = 4 self.expire_sec_2 = 4 self.tod_count = 0 expire1 = datetime.datetime.utcnow() + timedelta(seconds=self.expire_sec_1) expire2 = datetime.datetime.utcnow() + timedelta(seconds=self.expire_sec_2) # Create two timers times_of_day =[{'hour': str(expire1.hour),'minute' : str(expire1.minute), 'second':str(expire1.second) }, {'hour': str(expire2.hour),'minute' : str(expire2.minute), 'second':str(expire2.second)}] sub = EventSubscriber(event_type="ResourceEvent", callback=self.tod_callback, origin=event_origin) sub.start() # Expires in one days e = time.mktime((datetime.datetime.utcnow() + timedelta(days=1)).timetuple()) self.tod_sent_time = datetime.datetime.utcnow() id = ss.create_time_of_day_timer(times_of_day=times_of_day, expires=e, event_origin=event_origin, event_subtype="") self.assertEqual(type(id), str) gevent.sleep(15) # After waiting for 15 seconds, validate only 2 events are generated. self.assertTrue(self.tod_count == 2)
def on_start(self): #--------------------------------------------------------------------------------------------------- # Get the event Repository #--------------------------------------------------------------------------------------------------- self.event_repo = self.container.instance.event_repository self.smtp_client = setting_up_smtp_client() self.ION_NOTIFICATION_EMAIL_ADDRESS = '*****@*****.**' #--------------------------------------------------------------------------------------------------- # Create an event processor #--------------------------------------------------------------------------------------------------- self.event_processor = EmailEventProcessor(self.smtp_client) #--------------------------------------------------------------------------------------------------- # load event originators, types, and table #--------------------------------------------------------------------------------------------------- self.event_types = CFG.event.types self.event_table = {} #--------------------------------------------------------------------------------------------------- # Get the clients #--------------------------------------------------------------------------------------------------- self.discovery = DiscoveryServiceClient() self.process_dispatcher = ProcessDispatcherServiceClient() self.datastore_manager = DatastoreManager() self.event_publisher = EventPublisher() self.scheduler_service = SchedulerService()
def test_create_interval_timer(self): # test creating a new timer that is one-time-only # create the interval timer resource # create the event listener # call scheduler to set the timer # receive a few intervals, validate that arrival time is as expected # cancel the timer # wait until after next interval to verify that timer was correctly cancelled self.interval_timer_count = 0 self.interval_timer_sent_time = 0 self.interval_timer_received_time = 0 self.interval_timer_interval = 3 self.interval_timer_number_of_intervals = 4 event_origin = "Interval Timer" sub = EventSubscriber(event_type="ResourceEvent", callback=self.interval_timer_callback, origin=event_origin) sub.start() ss = SchedulerService() id = ss.create_interval_timer(start_time= time.time(), interval=self.interval_timer_interval, number_of_intervals=self.interval_timer_number_of_intervals, event_origin=event_origin, event_subtype="") self.interval_timer_sent_time = datetime.datetime.utcnow() self.assertEqual(type(id), str) # Wait until two events are published gevent.sleep((self.interval_timer_interval * 2) + 1) ss.cancel_timer(id) # Validate the timer id is invalid once it has been canceled with self.assertRaises(BadRequest): ss.cancel_timer(id) # Wait until all events are generated gevent.sleep(self.interval_timer_interval * self.interval_timer_number_of_intervals) # Validate events are not generated after canceling the timer self.assertEqual(self.interval_timer_count, 2)
class UserNotificationService(BaseUserNotificationService): """ A service that provides users with an API for CRUD methods for notifications. """ def on_start(self): #--------------------------------------------------------------------------------------------------- # Get the event Repository #--------------------------------------------------------------------------------------------------- self.event_repo = self.container.instance.event_repository self.smtp_client = setting_up_smtp_client() self.ION_NOTIFICATION_EMAIL_ADDRESS = '*****@*****.**' #--------------------------------------------------------------------------------------------------- # Create an event processor #--------------------------------------------------------------------------------------------------- self.event_processor = EmailEventProcessor(self.smtp_client) #--------------------------------------------------------------------------------------------------- # load event originators, types, and table #--------------------------------------------------------------------------------------------------- self.event_types = CFG.event.types self.event_table = {} #--------------------------------------------------------------------------------------------------- # Get the clients #--------------------------------------------------------------------------------------------------- self.discovery = DiscoveryServiceClient() self.process_dispatcher = ProcessDispatcherServiceClient() self.datastore_manager = DatastoreManager() self.event_publisher = EventPublisher() self.scheduler_service = SchedulerService() def on_quit(self): pass 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.") #--------------------------------------------------------------------------------------------------- # Persist Notification object as a resource if it has already not been persisted #--------------------------------------------------------------------------------------------------- # find all notifications in the system notifs, _ = self.clients.resource_registry.find_resources(restype = RT.NotificationRequest) # if the notification has already been registered, simply use the old id if notification in notifs: log.warning("Notification object has already been created in resource registry before for another user. No new id to be generated.") notification_id = notification._id else: # since the notification has not been registered yet, register it and get the id notification_id, _ = self.clients.resource_registry.create(notification) #------------------------------------------------------------------------------------------------------------------- # read the registered notification request object because this has an _id and is more useful #------------------------------------------------------------------------------------------------------------------- notification = self.clients.resource_registry.read(notification_id) #----------------------------------------------------------------------------------------------------------- # Create an event processor for user. This sets up callbacks etc. # As a side effect this updates the UserInfo object and also the user info and reverse user info dictionaries. #----------------------------------------------------------------------------------------------------------- user = self.event_processor.add_notification_for_user(notification_request=notification, user_id=user_id) #------------------------------------------------------------------------------------------------------------------- # Allow the indexes to be updated for ElasticSearch # We publish event only after this so that the reload of the user info works by the # notification workers work properly #------------------------------------------------------------------------------------------------------------------- # todo: This is to allow time for the indexes to be created before publishing ReloadUserInfoEvent for notification workers. # todo: When things are more refined, it will be nice to have an event generated when the # indexes are updated so that a subscriber here when it received that event will publish # the reload user info event. time.sleep(4) #------------------------------------------------------------------------------------------------------------------- # 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= "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 """ #------------------------------------------------------------------------------------------------------------------- # Get the old notification #------------------------------------------------------------------------------------------------------------------- old_notification = self.clients.resource_registry.read(notification._id) #------------------------------------------------------------------------------------------------------------------- # Update the notification #------------------------------------------------------------------------------------------------------------------- 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, old_notification) #------------------------------------------------------------------------------------ # Update the user_info dictionary maintained by UNS #------------------------------------------------------------------------------------ self.update_user_info_dictionary(user, notification, old_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." ) 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) self.event_processor.stop_notification_subscriber(notification_request=notification_request) #------------------------------------------------------------------------------------------------------------------- # delete the notification from the user_info and reverse_user_info dictionaries #------------------------------------------------------------------------------------------------------------------- self.delete_notification_from_user_info(notification_id) #------------------------------------------------------------------------------------------------------------------- # delete from the resource registry #------------------------------------------------------------------------------------------------------------------- self.clients.resource_registry.delete(notification_id) #------------------------------------------------------------------------------------------------------------------- # 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= "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 ''' for user_name, value in self.event_processor.user_info.iteritems(): for notif in value['notifications']: if notification_id == notif._id: # remove the notification value['notifications'].remove(notif) # remove the notification_subscription self.event_processor.user_info[user_name]['notification_subscriptions'].pop(notification_id) self.event_processor.reverse_user_info = calculate_reverse_user_info(self.event_processor.user_info) def find_events(self, origin='', type='', min_datetime='', max_datetime='', limit=-1, descending=False): """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_datetime str @param max_datetime str @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 """ if min_datetime and max_datetime: search_time = "SEARCH 'ts_created' VALUES FROM %s TO %s FROM 'events_index'" % (min_datetime, max_datetime) else: search_time = 'search "ts_created" is "*" from "events_index"' if origin: search_origin = 'search "origin" is "%s" from "events_index"' % origin else: search_origin = 'search "origin" is "*" from "events_index"' if type: search_type = 'search "type_" is "%s" from "events_index"' % type else: search_type = 'search "type_" is "*" from "events_index"' search_string = search_time + ' and ' + search_origin + ' and ' + search_type # get the list of ids corresponding to the events ret_vals = self.discovery.parse(search_string) log.debug("(find_events) Discovery search returned the following event ids: %s" % ret_vals) events = [] for event_id in ret_vals: datastore = self.datastore_manager.get_datastore('events') event_obj = datastore.read(event_id) events.append(event_obj) log.debug("(find_events) UNS found the following relevant events: %s" % events) if limit > -1: list = [] for i in xrange(limit): list.append(events[i]) return list #todo implement time ordering: ascending or descending return events def publish_event(self, event=None, scheduler_entry= None): ''' Publish a general event at a certain time using the UNS @param event Event @param scheduler_entry SchedulerEntry This object is created through Scheduler Service ''' log.debug("UNS to publish on schedule the event: %s" % event) #-------------------------------------------------------------------------------- # Set up a subscriber to get the nod from the scheduler to publish the event #-------------------------------------------------------------------------------- def publish(message, headers): self.event_publisher._publish_event( event_msg = event, origin=event.origin, event_type = event.type_) log.info("UNS published an event in response to a nod from the Scheduler Service.") event_subscriber = EventSubscriber( event_type = "ResourceEvent", callback=publish) event_subscriber.start() # Use the scheduler to set up a timer self.scheduler_service.create_timer(scheduler_entry) def create_worker(self, number_of_workers=1): ''' Creates notification workers @param number_of_workers int @ret_val 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' }) 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 = 0, end_time = 10): ''' 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. ''' for user_name, value in self.event_processor.user_info.iteritems(): notifications = value['notifications'] 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 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 ret_vals = self.discovery.parse(search_string) for event_id in ret_vals: datastore = self.datastore_manager.get_datastore('events') event_obj = datastore.read(event_id) events_for_message.append(event_obj) log.debug("Found following events of interest to user, %s: %s" % (user_name, 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, user_name) def format_and_send_email(self, events_for_message, user_name): ''' Format the message for a particular user containing information about the events he is to be notified about ''' message = str(events_for_message) log.info("The user, %s, will get the following events in his batch notification email: %s" % (user_name, message)) msg_body = '' count = 1 for event in events_for_message: # build the email from the event content msg_body += string.join(("\r\n", "Event %s: %s" % (count, event), "", "Originator: %s" % event.origin, "", "Description: %s" % event.description , "", "Event time stamp: %s" % event.ts_created, "\r\n", "------------------------" "\r\n")) count += 1 msg_body += "You received this notification from ION because you asked to be " + \ "notified about this event from this source. " + \ "To modify or remove notifications about this event, " + \ "please access My Notifications Settings in the ION Web UI. " + \ "Do not reply to this email. This email address is not monitored " + \ "and the emails will not be read. \r\n " log.debug("The email has the following message body: %s" % msg_body) msg_subject = "(SysName: " + get_sys_name() + ") ION event " self.send_batch_email( msg_body = msg_body, msg_subject = msg_subject, msg_recipient=self.event_processor.user_info[user_name]['user_contact'].email, smtp_client=self.smtp_client ) def send_batch_email(self, msg_body, msg_subject, msg_recipient, smtp_client): ''' Send the email ''' msg = MIMEText(msg_body) msg['Subject'] = msg_subject msg['From'] = self.ION_NOTIFICATION_EMAIL_ADDRESS msg['To'] = msg_recipient log.debug("EventProcessor.subscription_callback(): sending email to %s"\ %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, old_notification): ''' Update the UserInfo object. If the passed in parameter, od_notification, is None, it does not need to remove the old notification ''' #------------------------------------------------------------------------------------ # 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) notifications = [] for item in user.variables: if item['name'] == 'notifications': if old_notification and old_notification in item['value']: notifications = item['value'] # remove the old notification notifications.remove(old_notification) # put in the new notification notifications.append(new_notification) item['value'] = notifications break #------------------------------------------------------------------------------------ # update the resource registry #------------------------------------------------------------------------------------ self.clients.resource_registry.update(user) return user def update_user_info_dictionary(self, user, new_notification, old_notification): #------------------------------------------------------------------------------------ # Remove the old notifications #------------------------------------------------------------------------------------ if old_notification in self.event_processor.user_info[user.name]['notifications']: # remove from notifications list self.event_processor.user_info[user.name]['notifications'].remove(old_notification) #------------------------------------------------------------------------------------ # update the notification subscription object #------------------------------------------------------------------------------------ # get the old notification_subscription notification_subscription = self.event_processor.user_info[user.name]['notification_subscriptions'].pop(old_notification._id) # update that old notification subscription notification_subscription._res_obj = new_notification # feed the updated notification subscription back into the user info dictionary self.event_processor.user_info[user.name]['notification_subscriptions'][old_notification._id] = notification_subscription #------------------------------------------------------------------------------------ # find the already existing notifications for the user #------------------------------------------------------------------------------------ notifications = self.event_processor.user_info[user.name]['notifications'] notifications.append(new_notification) #------------------------------------------------------------------------------------ # update the user info - contact information, notifications #------------------------------------------------------------------------------------ self.event_processor.user_info[user.name]['user_contact'] = user.contact self.event_processor.user_info[user.name]['notifications'] = notifications self.event_processor.reverse_user_info = calculate_reverse_user_info(self.event_processor.user_info)