| /* | |
| * Copyright 2023 Google LLC | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #ifndef THIRD_PARTY_ECCLESIA_LIB_REDFISH_EVENT_SERVER_SUBSCRIPTION_H_ | |
| #define THIRD_PARTY_ECCLESIA_LIB_REDFISH_EVENT_SERVER_SUBSCRIPTION_H_ | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <functional> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <unordered_set> | |
| #include <vector> | |
| #include "absl/container/flat_hash_map.h" | |
| #include "absl/container/flat_hash_set.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/time/time.h" | |
| #include "absl/types/span.h" | |
| #include <nlohmann/json.hpp> | |
| namespace ecclesia { | |
| // Represents an event source identifier | |
| struct EventSourceId { | |
| // Enum representing the type of event source | |
| enum class Type : uint8_t { | |
| // D-Bus objects as event sources | |
| kDbusObjects = 0, | |
| // Socket IO as event sources | |
| kSocketIO = 1, | |
| // File IO as event sources | |
| kFileIO = 2 | |
| }; | |
| EventSourceId(absl::string_view key_in, Type type_in) | |
| : key(key_in), type(type_in) {} | |
| template <typename H> | |
| friend H AbslHashValue(H h, const EventSourceId &n) { | |
| return H::combine(std::move(h), n.key); | |
| } | |
| bool operator==(const EventSourceId &other) const { | |
| return key == other.key && type == other.type; | |
| } | |
| bool operator!=(const EventSourceId &other) const { | |
| return !(*this == other); | |
| } | |
| // Converts EventSourceId to JSON format | |
| nlohmann::json ToJSON() const; | |
| // Converts EventSourceId to string format | |
| std::string ToString() const; | |
| // Unique identifier for the event source | |
| std::string key; | |
| // Type of event source, represented by the Type enum | |
| Type type; | |
| }; | |
| // Uniquely identifies subscription. | |
| struct SubscriptionId { | |
| explicit SubscriptionId(size_t subscription_id_in) | |
| : subscription_id(subscription_id_in) {} | |
| template <typename H> | |
| friend H AbslHashValue(H h, const SubscriptionId &n) { | |
| return H::combine(std::move(h), n.subscription_id); | |
| } | |
| bool operator==(const SubscriptionId &other) const { | |
| return subscription_id == other.subscription_id; | |
| } | |
| bool operator!=(const SubscriptionId &other) const { | |
| return !(*this == other); | |
| } | |
| size_t Id() const { return subscription_id; } | |
| size_t subscription_id; | |
| }; | |
| // EventId uniquely identifies an event at any given point in time from any | |
| // event source. | |
| // | |
| // EventId satisfies the contract of uniqueness through `redfish_event_id` and | |
| // monotonicity through `timestamp`. `EventId` has an association with | |
| // `SubscriptionId` to allow subscription service to group events for a | |
| // subscriber, useful to satisfy lossless-events contract. | |
| struct EventId { | |
| EventId(const SubscriptionId &subscription_id_in, | |
| const EventSourceId &source_id_in, absl::Time timestamp_in); | |
| // Converts EventId to string format | |
| std::string ToString() const; | |
| // Converts EventId to JSON format | |
| nlohmann::json ToJSON() const; | |
| template <typename H> | |
| friend H AbslHashValue(H h, const EventId &n) { | |
| return H::combine(std::move(h), n.redfish_event_id); | |
| } | |
| bool operator==(EventId const& event_id) const { | |
| return ((event_id.source_id == this->source_id) && | |
| (event_id.timestamp == this->timestamp) && | |
| (event_id.subscription_id == this->subscription_id) && | |
| (event_id.redfish_event_id == this->redfish_event_id)); | |
| } | |
| bool operator!=(EventId const& event_id) const { | |
| return !(*this == event_id); | |
| } | |
| EventSourceId source_id; | |
| absl::Time timestamp; | |
| SubscriptionId subscription_id; | |
| size_t redfish_event_id; | |
| }; | |
| // Structure representing a Trigger, which defines event triggering conditions | |
| struct Trigger { | |
| using EventSourceToUri = | |
| absl::flat_hash_map<EventSourceId, std::vector<std::string>>; | |
| // Static method to create a Trigger object from raw data | |
| static absl::StatusOr<Trigger> Create(const nlohmann::json &trigger_json); | |
| // Constructor for Trigger | |
| explicit Trigger(absl::string_view id_in, | |
| absl::flat_hash_set<std::string> origin_resources_in, | |
| absl::string_view predicate_in = "", bool mask_in = false); | |
| // Trigger id | |
| std::string id; | |
| // Converts Trigger to JSON format | |
| nlohmann::json ToJSON() const; | |
| // Converts Trigger to string format | |
| std::string ToString() const; | |
| // List of origin resources associated with the Trigger | |
| absl::flat_hash_set<std::string> origin_resources; | |
| // Predicate expression for determining when to trigger the event | |
| std::string predicate; | |
| // Flag indicating whether to mask event source. | |
| bool mask; | |
| }; | |
| // Structure representing a subscription | |
| struct SubscriptionContext { | |
| SubscriptionContext( | |
| const SubscriptionId &subscription_id_in, | |
| const absl::flat_hash_map<EventSourceId, absl::flat_hash_set<std::string>> | |
| &event_source_to_uris_in, | |
| absl::flat_hash_map<std::string, Trigger> id_to_triggers_in, | |
| std::function<void(const nlohmann::json &)> &&on_event_callback_in); | |
| // Unique identifier for the subscription | |
| SubscriptionId subscription_id; | |
| // Map of event source IDs to corresponding URIs. | |
| // This map is used to process incoming events by looking up the URI an event | |
| // is associated with and then the URI is used to create an internal query | |
| // that builds `OriginOfCondtion`. | |
| absl::flat_hash_map<EventSourceId, absl::flat_hash_set<std::string>> | |
| event_source_to_uri; | |
| // Map of trigger IDs to corresponding Trigger objects | |
| // This map associates each trigger ID with the corresponding Trigger | |
| // object, enabling efficient lookup and management of triggers for a given | |
| // subscription. | |
| absl::flat_hash_map<std::string, Trigger> id_to_triggers; | |
| // Event callback to be invoked for events related to this subscription | |
| // This callback function is defined for each subscription and will be | |
| // called whenever an event occurs that matches the criteria defined by the | |
| // subscription's triggers. The callback function receives the event data as | |
| // its argument. | |
| std::function<void(const nlohmann::json &)> on_event_callback; | |
| // Peer's authenticated Redfish privileges | |
| std::unordered_set<std::string> privileges; | |
| }; | |
| // Interface for an event store. | |
| // EventStore stores events in an overwriting circular buffer and allows | |
| // looking up the events queued since a specific event_id to honor the | |
| // lossless eventing contract of subscription service. | |
| // | |
| // The event store shall be thread safe. | |
| class EventStore { | |
| public: | |
| virtual ~EventStore() = default; | |
| // Adds a new event to the overwriting circular buffer. | |
| virtual void AddNewEvent(const EventId &event_id, | |
| const nlohmann::json &event) = 0; | |
| // Retrieves all events that have been added since (but not including) the | |
| // given redfish event id in a chronological order determined by | |
| // `event_id.timestamp`. | |
| // If `redfish_event_id` is std::nullopt value, all events shall be returned | |
| // in chronological order. | |
| virtual std::vector<nlohmann::json> GetEventsSince( | |
| std::optional<size_t> redfish_event_id) = 0; | |
| // Retrieves the event with the given event id. | |
| virtual nlohmann::json GetEvent(const EventId &event_id) = 0; | |
| virtual std::vector<nlohmann::json> GetEventsBySubscriptionId( | |
| size_t subscription_id) = 0; | |
| // Report all events in the JSON format. | |
| virtual nlohmann::json ToJSON() = 0; | |
| // Report all events in the string format. | |
| virtual std::string ToString() = 0; | |
| // Cleat all events from the store. | |
| virtual void Clear() = 0; | |
| }; | |
| // Interface for a subscription store | |
| class SubscriptionStore { | |
| public: | |
| virtual ~SubscriptionStore() = default; | |
| // Adds a new subscription with the given subscription ID and event source IDs | |
| virtual absl::Status AddNewSubscription( | |
| std::unique_ptr<SubscriptionContext> subscription_context) = 0; | |
| // Deletes the subscription with the given subscription ID | |
| virtual void DeleteSubscription(const SubscriptionId &subscription_id) = 0; | |
| virtual absl::StatusOr<const SubscriptionContext *> GetSubscription( | |
| const SubscriptionId &subscription_id) = 0; | |
| // Retrieves the subscriptions associated with the given event source ID | |
| virtual absl::StatusOr<absl::Span<const ecclesia::SubscriptionContext *const>> | |
| GetSubscriptionsByEventSourceId(const EventSourceId &source_id) = 0; | |
| // Converts SubscriptionStore to JSON format | |
| virtual nlohmann::json ToJSON() = 0; | |
| // Converts SubscriptionStore to string format | |
| virtual std::string ToString() = 0; | |
| }; | |
| // Interface for a SubscriptionService backend. | |
| // This ensures that SubscriptionService interoperates with different Redfish | |
| // backends by standardizing the interface to subscribe and query Redfish | |
| // resources. | |
| class SubscriptionBackend { | |
| public: | |
| // Callback type for Redfish queries. | |
| using QueryCallback = | |
| std::function<void(const absl::Status & /*Status Code*/, | |
| const nlohmann::json & /*Redfish Resource json_str*/)>; | |
| // Callback type for Subscribe requests to SubscriptionBackend. | |
| // This callback shall be invoked with collection of EventSourceId objects on | |
| // a successful completion or an error in the status code on failed | |
| // subscription. | |
| using SubscribeCallback = | |
| std::function<void(const absl::Status & /*Status Code*/, | |
| const std::vector<EventSourceId> & /*EventSources*/)>; | |
| // Destructor for SubscriptionBackend | |
| virtual ~SubscriptionBackend() = default; | |
| // Performs a Redfish query at the given URL and invokes the provided callback | |
| // with the query result. | |
| // `peer_privileges` is the peer's Redfish privileges that will be used to | |
| // authorize the queried resources. | |
| virtual absl::Status Query( | |
| absl::string_view url, QueryCallback &&query_callback, | |
| const std::unordered_set<std::string> &peer_privileges) = 0; | |
| // Subscribes to Redfish events for the given URL and invokes the callback | |
| // post subscription. | |
| // `peer_privileges` is the peer's Redfish privileges that will be used to | |
| // authorize the subscribed resources. | |
| virtual absl::Status Subscribe( | |
| absl::string_view url, SubscribeCallback &&subscribe_callback, | |
| const std::unordered_set<std::string> &peer_privileges) = 0; | |
| }; | |
| // Interface for a subscription service | |
| class SubscriptionService { | |
| public: | |
| virtual ~SubscriptionService() = default; | |
| // Creates a new subscription and returns the subscription ID of the newly | |
| // created subscription. | |
| // Note: the implementation must guarantee that `on_event_callback` will only | |
| // be called once at a time. No parallel call will be allowed! | |
| virtual void CreateSubscription( | |
| const nlohmann::json &request, | |
| const std::unordered_set<std::string> &peer_privileges, | |
| std::function<void(const absl::StatusOr<SubscriptionId> &)> | |
| on_subscribe_callback, | |
| std::function<void(const nlohmann::json &)> on_event_callback) = 0; | |
| // Deletes the subscription with the given subscription ID | |
| virtual void DeleteSubscription(const SubscriptionId &subscription_id) = 0; | |
| // Retrieves all subscriptions managed by the service. | |
| virtual absl::Span<const SubscriptionContext> GetAllSubscriptions() = 0; | |
| // Returns all subscriptions in a JSON format. | |
| virtual nlohmann::json GetSubscriptionsToJSON() = 0; | |
| // Returns all subscriptions in a string format. | |
| virtual std::string GetSubscriptionsToString() = 0; | |
| // Returns all subscriptions in a JSON format. | |
| virtual nlohmann::json GetEventsToJSON() = 0; | |
| // Returns all subscriptions in a string format. | |
| virtual std::string GetEventsToString() = 0; | |
| // clear/flush all events in the event store. | |
| virtual void ClearEventStore() = 0; | |
| virtual nlohmann::json GetEventsBySubscriptionIdToJSON( | |
| size_t subscriber_id) = 0; | |
| // Invoked by an EventSource to notify SubscriptionService about an event | |
| // occurrence. | |
| // Returns error if notification cannot be processed, typically used to | |
| // indicate event source that subscription is deleted and the source should | |
| // disable event listener. | |
| virtual absl::Status Notify(EventSourceId event_source_id, | |
| const absl::Status &status) = 0; | |
| // Invoked by an EventSource to notify SubscriptionService about an event | |
| // occurrence along with providing data associated with the event. | |
| // This method is preferred when the event source is capable of constructing | |
| // entire OriginOfCondition for event. | |
| virtual absl::Status NotifyWithData(EventSourceId key, absl::Status status, | |
| const nlohmann::json &data) = 0; | |
| }; | |
| } // namespace ecclesia | |
| #endif // THIRD_PARTY_ECCLESIA_LIB_REDFISH_EVENT_SERVER_SUBSCRIPTION_H_ |