Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3124223
Added test for ResourceChanged Event
rherrell Apr 3, 2026
a60bd78
fixed a bug in the new_event() method of core.py
rherrell Apr 6, 2026
63d0bb5
added test for event forwarding for subordindate objects changed
rherrell Apr 9, 2026
721a8ee
fixed a broken loop in forward_event()
rherrell Apr 10, 2026
d703b72
adding path to create events in ResourceEvent prefix class
rherrell Apr 23, 2026
70ed143
mods to enable create_object to issue ResourceCreated events to clients
rherrell May 8, 2026
39bf167
events from Agents should not be arbitrarily forwarded
rherrell May 15, 2026
b47d989
remove event forwarding in handle_event -- not allowed
rherrell May 20, 2026
ec18e16
restructured handle_event() and associated tests
rherrell Jun 5, 2026
4acd74c
added Event generation to resourceChanged handler
rherrell Jun 11, 2026
507f08c
commented out a pdb.set_trace()
rherrell Jun 11, 2026
39f0116
added more Event generation for DELETE actions
rherrell Jul 7, 2026
272a24c
enabled capturing of resourceType before resource was deleted by back…
rherrell Jul 7, 2026
972e165
fixed the delete_subscription() call from the redfish_object_handler …
rherrell Jul 15, 2026
8379e59
properly removes subordinate files AND links to the removed files and…
rherrell Jul 23, 2026
ebcc764
added renaming tests and alias database cleansing
rherrell Jul 31, 2026
00bc500
fixed bugs in forward_to_agent flow and core.py
rherrell Sep 4, 2026
ba6b645
added Sunfish alias removal to client API's DELETE function
rherrell Sep 10, 2026
8221b9a
changed event handler return values to fix type mismatches
rherrell Sep 25, 2026
b0cac29
added fix to logger.debug() formatting
rherrell Oct 1, 2026
aa734b9
need to add missing AggregationSource in tests dir
rherrell Oct 2, 2026
8f5e9b8
changed expected responses in pytest scripts to match new handle_even…
rherrell Oct 2, 2026
0697d25
fixed a fragile line and one comment
rherrell Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions sunfish/events/redfish_subscription_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import os
import string
import pdb

from sunfish.events.subscription_handler_interface import SubscriptionHandlerInterface
from sunfish.lib.exceptions import *
Expand Down Expand Up @@ -62,6 +63,7 @@ def __init__(self, core):

# Loads the subscriptions already stored
def load_subscriptions(self):
#pdb.set_trace()
path = os.path.join(os.getcwd(), self.fs_root, self.subscribers_root)
if not os.path.exists(path):
return
Expand All @@ -80,6 +82,7 @@ def load_subscriptions(self):

def new_subscription(self, payload: dict):
# check if sub has colliding properties
#pdb.set_trace()
if self.validate_subscription(payload) is False:
raise IllegalSubscription

Expand Down
87 changes: 69 additions & 18 deletions sunfish/lib/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# The full license terms are available here: https://github.com/OpenFabrics/sunfish_library_reference/blob/main/LICENSE

import os
import json
import string
import uuid
import logging
Expand Down Expand Up @@ -208,12 +209,10 @@ def create_object(self, path: string, payload: dict):
# 1. check the path target of the operation exists
# self.storage_backend.read(path)
# above done elsewhere, too soon to do here
# 2. is needed first forward the request to the agent managing the object
# 2. if needed first forward the request to the agent managing the object
agent_response = self.objects_manager.forward_to_manager(SunfishRequestType.CREATE, path, payload=payload)
if agent_response:
payload_to_write = agent_response
# 3. Execute any custom handler for this object type AFTER Agent mods, if any
self.objects_handler.dispatch(object_type, path, SunfishRequestType.CREATE, payload=payload_to_write)
except ResourceNotFound:
logger.error("The collection where the resource is to be created does not exist.")
except AgentForwardingFailure as e:
Expand All @@ -222,8 +221,16 @@ def create_object(self, path: string, payload: dict):
# The object does not have a handler.
logger.debug(f"The object {object_type} does not have a custom handler")
pass
# 3. Execute any custom handler for this object type AFTER Agent mods, if any
self.objects_handler.dispatch(object_type, path, SunfishRequestType.CREATE, payload=payload_to_write)
# 4. persist change in Sunfish tree
return self.storage_backend.write(payload_to_write)
payload_written = self.storage_backend.write(payload_to_write)
# 5. create appropriate Event and send to subscribed EventDestinations
generate_resource_event = self.event_handler.resource_event_builder(SunfishRequestType.CREATE, path, payload=payload_written)
self.event_handler.new_event(generate_resource_event)


return payload_written

def replace_object(self, path: str, payload: dict):
"""Calls the correspondent replace function from the backend implementation.
Expand All @@ -242,21 +249,25 @@ def replace_object(self, path: str, payload: dict):
try:
# 1. check the path target of the operation exists
self.storage_backend.read(path)
# 2. is needed first forward the request to the agent managing the object
#self.objects_manager.forward_to_manager(SunfishRequestType.REPLACE, path, payload=payload)
# 2. if needed first forward the request to the agent managing the object
agent_response = self.objects_manager.forward_to_manager(SunfishRequestType.REPLACE, path, payload=payload)
if agent_response:
payload_to_write = agent_response
# 3. Execute any custom handler for this object type
self.objects_handler.dispatch(object_type, path, SunfishRequestType.REPLACE, payload=payload_to_write)
except ResourceNotFound:
logger.error(logger.error(f"The resource to be replaced ({path}) does not exist."))
except AttributeError:
# The object does not have a handler.
logger.debug(f"The object {object_type} does not have a custom handler")
pass
# 3. Execute any custom handler for this object type
self.objects_handler.dispatch(object_type, path, SunfishRequestType.REPLACE, payload=payload_to_write)
# 4. persist change in Sunfish tree
return self.storage_backend.replace(payload_to_write)
payload_written = self.storage_backend.replace(payload_to_write)
# 5. create appropriate Event and send to subscribed EventDestinations
generate_resource_event = self.event_handler.resource_event_builder(SunfishRequestType.REPLACE, path, payload=payload_written)
self.event_handler.new_event(generate_resource_event)

return payload_written

def patch_object(self, path: str, payload: dict):
"""Calls the correspondent patch function from the backend implementation.
Expand All @@ -277,21 +288,25 @@ def patch_object(self, path: str, payload: dict):
# 1. check the path target of the operation exists
self.storage_backend.read(path)
# 2. is needed first forward the request to the agent managing the object
#self.objects_manager.forward_to_manager(SunfishRequestType.PATCH, path, payload=payload)
agent_response = self.objects_manager.forward_to_manager(SunfishRequestType.PATCH, path, payload=payload)
if agent_response:
payload_to_write = agent_response
# 3. Execute any custom handler for this object type
self.objects_handler.dispatch(object_type, path, SunfishRequestType.PATCH, payload=payload)
except ResourceNotFound:
logger.error(f"The resource to be patched ({path}) does not exist.")
except AttributeError:
# The object does not have a handler.
logger.debug(f"The object {object_type} does not have a custom handler")
pass

# 3. Execute any custom handler for this object type
self.objects_handler.dispatch(object_type, path, SunfishRequestType.PATCH, payload=payload)
# 4. persist change in Sunfish tree
return self.storage_backend.patch(path, payload_to_write)
payload_written = self.storage_backend.patch(path, payload_to_write)
# 5. create appropriate Event and send to subscribed EventDestinations
generate_resource_event = self.event_handler.resource_event_builder(SunfishRequestType.PATCH, path, payload=payload_written)
self.event_handler.new_event(generate_resource_event)

return payload_written

def delete_object(self, path: string):
"""Calls the correspondent remove function from the backend implementation. Checks that the path is valid.
Expand All @@ -312,16 +327,22 @@ def delete_object(self, path: string):
self.storage_backend.read(path)
# 2. is needed first forward the request to the agent managing the object
self.objects_manager.forward_to_manager(SunfishRequestType.DELETE, path)
# 3. Execute any custom handler for this object type
self.objects_handler.dispatch(object_type, path, SunfishRequestType.DELETE)
except ResourceNotFound:
logger.error(f"The resource to be deleted ({path}) does not exist.")
except AttributeError:
# The object does not have a handler.
logger.debug(f"The object {object_type} does not have a custom handler")

# 3. Execute any custom handler for this object type
self.objects_handler.dispatch(object_type, path, SunfishRequestType.DELETE)
# 4. persist change in Sunfish tree
self.storage_backend.remove(path)
list_of_impacted_objects = self.storage_backend.remove(path)
# 5. process list of impacted objects for subscribers to ResourceEvents
events_sent_to = self.event_handler.process_new_resourceEvents(list_of_impacted_objects)
# 6. remove any deleted objects' URIs from Sunfish alias DB
#pdb.set_trace()
events_sent_to = self.event_handler.removeAliasesFromSunfishDB(list_of_impacted_objects)
# TODO
return f"Object {path} deleted"

def handle_event(self, payload):
Expand All @@ -331,15 +352,45 @@ def handle_event(self, payload):
else:
context = ""
logger.debug("Started handling incoming events")
sunfish_handled = False
all_event_responses = []
this_event_response = {}
stat_code_max = 0

for event in payload["Events"]:
logger.debug(f"Handling event {event['MessageId']}")
message_id = event['MessageId'].split(".")[-1]
event_id = event.get('EventId') or ""
event_origin = event.get('OriginOfCondition') or {}
stat_code = 500
try:
self.event_handler.dispatch(message_id, self.event_handler, event, context)
resp = self.event_handler.dispatch(message_id, self.event_handler, event, context)
if resp is not None:
#pdb.set_trace()
sunfish_handled = True
if type(resp) == int:
stat_code = resp
this_event_response["EventId"]=event_id
this_event_response["MessageId"]=message_id
this_event_response["dispatch_response"]=stat_code
this_event_response["origin"]=event_origin
all_event_responses.append(this_event_response)
if stat_code > stat_code_max:
stat_code_max = stat_code

except PropertyNotFound as e:
logger.warning(repr(e))
raise e
return self.event_handler.new_event(payload)

# if no events are handled by Sunfish, do NOT forward the original event to any subscribers
# for now return an unhandled response
if sunfish_handled is False:
return {"status": "un-processable content", "code": 422}
else:
if stat_code_max < 200:
stat_code_max = 200
logger.info(f"event handler returned these results: \n {json.dumps(all_event_responses, indent = 4)}")
return {"status": "success", "code": 200}

def _get_type(self, payload: dict, path: str = None):
# controlla odata.type
Expand Down
Loading
Loading