Giter Club home page Giter Club logo

openedx-events's Introduction

Open edX Events

PyPI CI Codecov Documentation Supported Python versions License status-badge

Open edX Events from Hooks Extensions Framework (OEP-50).

Purpose

This repository implements the necessary tooling and definitions used by the Hooks Extension Framework to manage the events execution and extra tools.

Getting Started with Development

Please see the Open edX documentation for guidance on Python development in this repo.

Deploying

The Open edX Events component is a Python library which doesn't need independent deployment. Therefore, its setup is reasonably straightforward. First, it needs to be added to your service requirements, and then it will be installed alongside requirements of the service.

If the service you intend to use is either the LMS or CMS, then the library is installed alongside their requirements since the Maple release.

Getting Help

Documentation

See documentation on Read the Docs.

More Help

If you're having trouble, we have discussion forums at https://discuss.openedx.org where you can connect with others in the community.

Our real-time conversations are on Slack. You can request a Slack invitation, then join our community Slack workspace.

For anything non-trivial, the best path is to open an issue in this repository with as many details about the issue you are facing as you can provide.

https://github.com/openedx/openedx-events/issues

For more information about these options, see the Getting Help page.

License

The code in this repository is licensed under the Apache 2.0 license unless otherwise noted.

Please see LICENSE.txt for details.

Contributing

Contributions are very welcome. Please read How To Contribute for details.

This project is currently accepting all types of contributions, bug fixes, security fixes, maintenance work, or new features. However, please make sure to have a discussion about your new feature idea with the maintainers prior to beginning development to maximize the chances of your change being accepted. You can start a conversation by creating a new issue on this repo summarizing your idea.

The Open edX Code of Conduct

All community members are expected to follow the Open edX Code of Conduct.

People

The assigned maintainers for this component and other project details may be found in Backstage. Backstage pulls this data from the catalog-info.yaml file in this repo.

Reporting Security Issues

Please do not report security issues in public. Please email [email protected].

openedx-events's People

Contributors

bmtcril avatar bryanttv avatar dependabot[bot] avatar edx-requirements-bot avatar feanil avatar felipemontoya avatar ian2012 avatar ilee2u avatar irfanuddinahmad avatar jinder1s avatar juandavidbuitrago avatar kdmccormick avatar kyrylo-kh avatar mariajgrimaldi avatar morenol avatar navinkarkera avatar nedbat avatar nikolayborovenskiy avatar rgraber avatar robrap avatar rpenido avatar saadyousafarbi avatar sarina avatar timmc-edx avatar usamasadiq avatar whuang1202 avatar xitij2000 avatar yusuf-musleh avatar zacharis278 avatar zubairshakoorarbisoft avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

openedx-events's Issues

[OEP-55] Standardize unittests

There's a need to:

  • Increase coverage adding unittesting to not tested components
  • Add docstrings to unittests
  • Add documentation on how to add unittests (structure)
  • Use best practices when testing

[CloudEvents] Field ce_time for event bus events

This is a continuation of #77, where we decided to defer the CloudEvent field ce_time, as defined in OEP-41.

  • There is lots of discussion on "time" in the earlier #77. There are decisions to be made, and then follow-up implementation.

What to send over the wire (kafka)?

Currently, arch-bom, a team at edX, is testing MVP Kafka event bus integration into the Open edX platform. During the implementation, a couple of questions were raised and github issues seems like a great public venue to have the discussion.

Questions:

  1. What is sent over the over the wire in Kafka?
    The options are:
    a. The serialized versions of the attrs decorated classes implemented in data.py
    b. The serialized version of signal classes implemented in signal.py
    c. The serialized version of data attribute in signal classes. Example
  2. What is sent by a django signals sender?
    I do not know django signals very well, so I curious if the intention is for django signals to send the signals classes implemented in signal.py. Or will the signal sender also send exactly the same information as a Kafka producer.
  3. Depending on which option we choose above for Kafka, what happens when we need to change data definition(schema)? Specifically, which unit changes?
    Choices:
    a. Attrs decorated classes in data.py
    b. signals class in signal.py
    c. Both??

My main goal for this discussion is pinpointing what to use to create a schema for Kafka. So far, I've been using the Attrs decorated classes in data.py.

[Abstraction] Decide how to handle key deserialization when key schema might vary

In the current event bus code, it is possible for signals to be broadcast as events using varying key IDs on the same topic. For example, the CERTIFICATE_CREATED signal could be emitted as events with a key taken from certificate.user.id, or a key taken from certificate.course.course_key. This presents a problem for deserialization: One of those is a number, and the other is a string; deserialization of the key will fail if the key schema is not knowable from the event.

I can see several possible solutions:

  • By documented convention, never use the same topic to send events for the same signal but with differing key-fields
    • Producer and consumer code would continue to explicitly specify "produce on/listen to topic X for signal Y with key-field Z"
    • Kind of fragile and redundant
  • Have the producer include the key-field in the event headers ("key_field": "certificate.user.id") so that the consumer can tell how to deserialize the key.
    • Self-documenting in a way that should be pretty robust; would handle schema evolution very easily.
  • Specify the key-field on the OpenEdxPublicSignal itself, so that signals can always only be sent with the same key-field.
    • Less flexible, although there's a migration path for changing that.
    • Simpler.
    • Conflates signals and events maybe a little more than we want.
    • Not clear how we'd handle composite keys such as we'd likely need for certificates -- combine the user and course identifiers by using a list of key-fields? Have a blank key?
  • Obviate the whole issue by never deserializing keys, and just emitting them as bytestrings when using them in logging code and similar.
    • It's possible we don't really have a use for keys beyond debugging and other observability.

[OEP-55] Improve library documentation

Description

For a new adopter, it should be clear:

  • How to use events
  • How to create a new event
  • Relevant technical details
  • Which events are maintained?
  • Library roadmap

[Abstraction] Configurable loading of Event Bus producer implementation

We need a way to load event-bus implementations based on configuration, so that IDAs don't have to depend directly on those libraries.

This issue is to define and implement an interface that allows an IDA to retrieve a producer instance by looking at Django settings. Consumer side has been split off to #147 since it is lower priority.

  • Define interface in openedx-events -- a documented set of functions/module variables and a loader that reads the settings. PR: #85
  • Implement producer API in event-bus-kafka. PR: openedx/event-bus-kafka#14
  • Add link from openedx-events producer API docs to event-bus implementation (to better document the config with a real example): #150
  • Switch CMS and any other producers to use new interface and remove any current direct references to the edx_event_bus_kafka module that are present: openedx/edx-platform#31356

Distinguish between web and worker in EventsMetadata.source

Right now, the EventsMetadata object is hardcoded to identify the source as "openedx/{service}/web." In order to be compliant with https://open-edx-proposals.readthedocs.io/en/latest/architectural-decisions/oep-0041-arch-async-server-event-messaging.html#id3, we should be distinguishing between web and workers. It's not immediately obvious to me where we can get this information. The only setting I can find that distinguishes web from worker is NEW_RELIC_APP_NAME, which seems very fragile.

Remove signal parameter from consumer

The signal parameter of the consumer init method and the management command have been deprecated and should be removed. It may be necessary to make them optional first, release that, then remove them as a new major version.

A/C:

  • Make optional in openedx-events (and event-bus-redis if it isn't already. it's already optional in event-bus-kafka)
  • Remove parameter from all active consumers
    • make sure to test on stage before removing in prod
  • Remove parameter from openedx-events
  • Remove parameter from EBK (and let Axim know they can remove it from EBR)

[Event Bus] Implement sad-path for simple Studio publish events

This epic is part of the Event Bus work documented in openedx/platform-roadmap#28.

The epic covers the implementation of the sad-paths for a new use case. The current plan is to implement the use case of course publishing from Studio that could be consumed by Discovery, potentially replacing a small piece of the Refresh Course Metadata batch job. For the purposes of this Epic, we will handle any error or exceptional conditions. This may include:

  • Publishing cached events on server shutdown.
  • Error handling while publishing events.
  • Error handling while consuming events.
    • Likely to include a Dead Letter Queue for events that can't be processed.

The code developed during this epic will have an eye toward reusability and pluggability, but will err toward getting something worked so that the API can be defined in a later iteration.

Milestone Details:

  • Title: [Event Bus] Implement sad-path
  • Description: See https://github.com/openedx/openedx-events/issues/90 for Epic details.
  • Due Date: TBD
    • Important: Date changes need to be fixed in all repos with this milestone defined.

Open issues/PRs with this milestone: https://github.com/pulls?q=is%3Aopen+milestone%3A%22%5BEvent+Bus%5D+Implement+sad-path%22

[Event Bus] Production ready for simple Studio publish events

This epic is part of the Event Bus work documented in openedx/platform-roadmap#28.

The epic covers remaining work for having the new use case in Production, after completing happy-path and sad-path work in other epics. The use case implemented is for course publishing from Studio that could be consumed by Discovery, potentially replacing a small piece of the Refresh Course Metadata batch job. For the purposes of this Epic, we will handle any remaining work, including:

  • Production Kafka configuration.
  • Observability
  • Any other required work that comes up.

Also, adding some preparation work for others coming on to the Event Bus.

Milestone Details:

  • Title: [Event Bus] Production ready event
  • Description: See https://github.com/openedx/openedx-events/issues/91 for Epic details.
  • Due Date: TBD
    • Important: Date changes need to be fixed in all repos with this milestone defined.

Open issues/PRs with this milestone: https://github.com/pulls?q=is%3Aopen+milestone%3A%22%5BEvent+Bus%5D+Production+ready+event%22

Consuming event bus events

In the ADR (https://github.com/openedx/openedx-events/blob/main/docs/decisions/0004-external-event-bus-and-django-signal-events.rst), we discussed firing the same event signal after consuming an event bus event.

An advantage of this proposal is it mimics what is done for in-process events.

However, there is a major difference between in-process events and events in the consuming application. For in-process events, we must use a technology like celery to get asynchronous processing of the events. However, our event bus consuming applications are already asynchronous in relation to the main user/api-facing service.

For these applications, we have an opportunity to simply do the event processing in-process from the point of view of the consuming application, and to horizontally scale the consumer containers based on processing requirements. We could do this without spinning up separate celery tasks that will then compete with other tasks in the same celery queue.

  • Should each consumer application only handle one type of processing per event within itself?
  • Are signals still a good pattern for making the processing pluggable, or does it lead one to assume that multiple receivers may be configured? Are we ok with multiple signal receivers?
  • If multiple signal receivers are used, would we have recommendations on whether or not some or all of them are allowed to processing within the consumer, vs spinning up a separate task in celery?
  • If we want only one processor per consumer, should we make a different pluggable interface that is more like a singleton receiver?

Note, I personally would like to take advantage of processing in the consumers and avoiding celery to start. We don't need to make any final decisions here, but it would be nice to have a nice initial recommendation.

Thank you.

[Discovery] Handle multiple lifecycle events on single event bus topic

This is a bit of a discovery ticket.

Acceptance Criteria:

  • ADR for handling of multiple lifecycle events (created, updated, deleted) on a single topic.
  • Update how-to as appropriate.
  • Create follow-up ticket (if needed) for any work that would enable onboarding new multi-event type topics sooner rather than later.

Notes:

Questions:

  • Will we follow Confluent’s recommendation to use a single topic for multiple related events?
    • How will we get a partition key from our current event definition?
    • How will we handle the schema used for the topic to allow for the multiple event types?
  • Will this solution work for other event bus technologies other than Kafka?
  • What are good naming conventions for the event_type, event definition (signal), data class, etc. for the proposed solution?

Event to Topic relationship

In openedx/edx-platform#32249, we had a discussion about the notifications app that veered into message bus topic/event mapping questions that deserved its own separate conversation.

@robrap wrote:

Thanks @ormsbee. Without my getting lost in notifications design considerations, using existing events, new events, and the event bus all makes sense to me.

I've certainly read that only one service should be responsible for writing to any given topic. I'm not sure if the following question applies to this use case, but is this a useful rule or not? If it comes up, we'd need to determine if it makes sense to have a shared topic that any service could write to and is used as a single pipe into some other service, or if we have a service that subscribes to all of the topics that include some event, like USER_NOTIFICATION, and either responds immediately or writes it to a new consolidated topic. This is just an implementation detail about how to use the event bus, and isn't about whether or not to use the event bus.

@ormsbee replied:

I've certainly read that only one service should be responsible for writing to any given topic.

I think it's sane as long as it's either one writer with potentially many consumers, or many writers with one consumer. For auditing purposes, things like PII would also make sense to have many writers, one consumer.

If it comes up, we'd need to determine if it makes sense to have a shared topic that any service could write to and is used as a single pipe into some other service, or if we have a service that subscribes to all of the topics that include some event, like USER_NOTIFICATION, and either responds immediately or writes it to a new consolidated topic.

I would favor having a single topic for all user notifications to go to, partitioned by a hash of the user.

Apologies, but I haven't been following event type -> topic mapping discussions. I had thought that it would be based on the event types themselves, and not the sending or receiving apps. I don't want to derail this ticket into that discussion, but do you have a link to the ticket where that was hashed out? I vaguely recall skimming something a while back, but I can't seem to find it.

@robrap replied:

  • When producing, you produce an event (with an event type) to a topic. Unfortunately, it is possible to produce an event to the wrong topic, but no one is likely to listen to it, so it will just die out once fixed.
  • When consuming, the management command consumes from a topic. All event types on the topic will be converted to signals, and only those signals with receiver code will be handled. See https://openedx.atlassian.net/wiki/spaces/AC/pages/3508699151/How+to+start+using+the+Event+Bus#Consuming-the-signal.
    I wouldn't be able to point to specific issues around this except the recent #78, which was around how to implement the capability of sending multiple event types to a single topic.

I think it's sane as long as it's either one writer with potentially many consumers, or many writers with one consumer.

I imagine that the many writers to one consumer pattern would have a definite initial consumer and purpose in mind. However, I'm not sure that a second consumer might not come along and say "Hey, that's the perfect set of events that I want for my own purpose." Would it be an issue for there to be a second consumer?

If we want to allow many producers producing to a single topic, I think the important point would be that both producers and consumers of that topic should have this shared understanding from the start, and it should be well documented. In other words, it would not be ok for a service to see an event that it could also fire, and just start firing it into a previously existing topic.

Enable producing to event bus via settings

At this time, producing a signal to the event bus requires code. See the event bus how-to for details.

This is problematic because:

  1. Nearly all openedx-events signals should be able to be produced to the event bus, and different operators may have different needs.
  2. Related, for named releases, operators should be able to produce new events without changing code or waiting for the next named release.

This issue proposes to make the switch to configuring this via settings, rather than code.

Some thoughts/questions about the settings:

  1. It should be possible to toggle on/off an event as we do in code. I imagine that we'd want to be able to do this without having to add/delete all config for a topic, and could instead set a flag.
  2. Does the setting need to support producing the same event to multiple topics? Maybe not? I can't remember where we landed for migrations, and maybe we could add this later while keeping a simpler one-topic setup by default?
  3. The settings should support multiple events going to the same topic. See this lifecycle event issue for details on where we landed on this: #78.
  4. It would be great if services could include default settings for signals that are expected to be produced. These may be toggled off, or incomplete, but could be used as documentation of what signals should be configured. (Or, someone can provide an alternative solution to this particular issue.)
  5. This may be best covered in an entirely separate ticket, but it would be nice if we could toggle some sort of audit that would help estimate the data that would be sent to the event bus if a event were sent there. This could be used for making cost estimates for existing events.
  6. Reminder: replace existing event producing code with this config when it is available.

[Support] Compatibility with Python3.10

Description

Trying to add python3.10 support on eox-hooks fails due to openedx-events.

Versions

  • ENV: Python 3.10.6

Error

ERROR: test_receiver_called_after_cert_change (eox_hooks.tests.test_receivers.TestCohortEventsHandler)
Test that hooks_handler is called the correct information after sending
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/django/test/utils.py", line 387, in inner
    return func(*args, **kwargs)
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/mock/mock.py", line 1346, in patched
    return func(*newargs, **newkeywargs)
  File "/home/edunextdev/Documents/edunext/eox-hooks/eox_hooks/tests/test_receivers.py", line 481, in test_receiver_called_after_cert_change
    COHORT_MEMBERSHIP_CHANGED.send_event(
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/openedx_events/tooling.py", line 170, in send_event
    format_responses(responses, depth=2),
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/openedx_events/utils.py", line 90, in format_responses
    ).pformat(obj)
  File "/usr/lib/python3.10/pprint.py", line 157, in pformat
    self._format(object, sio, 0, 0, {}, 0)
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/openedx_events/utils.py", line 31, in _format
    if isinstance(obj, collections.Callable):
AttributeError: module 'collections' has no attribute 'Callable'

======================================================================
ERROR: test_receiver_called_after_enroll_change (eox_hooks.tests.test_receivers.TestEnrollmentEventsHandler)
Test that hooks_handler is called the correct information after sending
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/django/test/utils.py", line 387, in inner
    return func(*args, **kwargs)
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/mock/mock.py", line 1346, in patched
    return func(*newargs, **newkeywargs)
  File "/home/edunextdev/Documents/edunext/eox-hooks/eox_hooks/tests/test_receivers.py", line 263, in test_receiver_called_after_enroll_change
    COURSE_ENROLLMENT_CHANGED.send_event(
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/openedx_events/tooling.py", line 170, in send_event
    format_responses(responses, depth=2),
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/openedx_events/utils.py", line 90, in format_responses
    ).pformat(obj)
  File "/usr/lib/python3.10/pprint.py", line 157, in pformat
    self._format(object, sio, 0, 0, {}, 0)
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/openedx_events/utils.py", line 31, in _format
    if isinstance(obj, collections.Callable):
AttributeError: module 'collections' has no attribute 'Callable'

======================================================================
ERROR: test_receiver_called_after_enrollment (eox_hooks.tests.test_receivers.TestEnrollmentEventsHandler)
Test that hooks_handler is called the correct information after sending
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/django/test/utils.py", line 387, in inner
    return func(*args, **kwargs)
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/mock/mock.py", line 1346, in patched
    return func(*newargs, **newkeywargs)
  File "/home/edunextdev/Documents/edunext/eox-hooks/eox_hooks/tests/test_receivers.py", line 231, in test_receiver_called_after_enrollment
    COURSE_ENROLLMENT_CREATED.send_event(
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/openedx_events/tooling.py", line 170, in send_event
    format_responses(responses, depth=2),
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/openedx_events/utils.py", line 90, in format_responses
    ).pformat(obj)
  File "/usr/lib/python3.10/pprint.py", line 157, in pformat
    self._format(object, sio, 0, 0, {}, 0)
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/openedx_events/utils.py", line 31, in _format
    if isinstance(obj, collections.Callable):
AttributeError: module 'collections' has no attribute 'Callable'

======================================================================
ERROR: test_receiver_called_after_unenrollment (eox_hooks.tests.test_receivers.TestEnrollmentEventsHandler)
Test that hooks_handler is called the correct information after sending
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/django/test/utils.py", line 387, in inner
    return func(*args, **kwargs)
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/mock/mock.py", line 1346, in patched
    return func(*newargs, **newkeywargs)
  File "/home/edunextdev/Documents/edunext/eox-hooks/eox_hooks/tests/test_receivers.py", line 295, in test_receiver_called_after_unenrollment
    COURSE_UNENROLLMENT_COMPLETED.send_event(
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/openedx_events/tooling.py", line 170, in send_event
    format_responses(responses, depth=2),
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/openedx_events/utils.py", line 90, in format_responses
    ).pformat(obj)
  File "/usr/lib/python3.10/pprint.py", line 157, in pformat
    self._format(object, sio, 0, 0, {}, 0)
  File "/home/edunextdev/Documents/edunext/eox-hooks/env/lib/python3.10/site-packages/openedx_events/utils.py", line 31, in _format
    if isinstance(obj, collections.Callable):
AttributeError: module 'collections' has no attribute 'Callable'

Make ADR for versioning/dependency strategy around event bus implementations

A/C

  • An ADR explaining why we're going to depend on all implementations of the event bus: #227
  • An issue for doing this better in the future (possibly an Axim issue)
    • Declined -- we're aware of the broader plugin dependencies issue and anything we do to solve that will help with the event bus implementations.

We reached a working consensus that we would depend on all implementations from any IDA that needed to use the Event Bus, but never made an ADR out of it: #88 (comment)

Let's finish that process.

[Tests] Should fastavro dependency be constrained?

We ran into an issue where fastavro was updated in services and started error-ing on events: #137. It turns out these errors were for a new validation check in fastavro that caught something we were doing that it did not expect. Our unit tests did begin failing with this change.

One idea is to constrain fastavro in openedx-events so that services would not have upgraded until tests passed in this repo. Here is a sample PR for that: #139.

The upside to constraining would be that a fastavro bug would not be introduced that fails our tests.
The downside is that fastavro would require manual upgrades, and we don't have a great process for ensuring that would happen. Additionally, in the above case, in theory we would have been producing/consuming invalid data and would have had a challenging time upgrading. But, maybe that would have been better than finding out through a set of failed event consumption.

Tweak metadata-conveying send-event method to avoid possible kwarg collisions

send_event_with_custom_metadata currently takes metadata keys individually as kwargs, but it turns out to be awkward, as in practice in event-bus-kafka we end up building an EventsMetadata and then splatting it into the call along with the data dict: https://github.com/openedx/event-bus-kafka/blob/51b0055990ed0116dc06781d6e1c8f90959eb088/edx_event_bus_kafka/internal/consumer.py#L365

This also increases risk of collisions. #177 would address that, but alternatively we could just accept a single metadata arg (positionally?) as a breaking change.

Codecov setup

Hello!
This repo doesn't have a codecov setup for the Open edX organization, what can we do to configure it?

Discovery: Signal metadata, event bus consumers, and timestamps

We recently ran into an issue where we want our event bus signal consumer logic to know the broker's timestamp of the message. However, that is not available in the signal handler, and brings up a number of issues:

  1. If an event bus signal consumer looked at the "metadata" dict in the kwargs, what data would it find? Some valid and some invalid data? All invalid? Other?
  • Note: The signal currently has a time metadata with now() as the implementation, which would give the consumer time.
  1. Is it safe to assume that the metadata on the consumer should match what would be seen on the producer? How should this data be sent, and where should the metadata be updated? Note that most of this data is sent in the message headers as a start.
  2. If we want to send event bus specific data to the signal handler, like the event bus broker's timestamp for the message, how would we do this?
  3. Is there a way to not be blocked on #159 to find a single definition of "time"? Can we avoid that field, and use more specifically named and defined fields?

A/C:

  • Ensure the consumer has access to the time when an event was produced
  • Ensure the consumer can send the original event metadata when it emits a signal (or at the very least, the signal handler has access to that data)
  • Update docs accordingly

Additional tasks:

  • openedx-events requires update to accept time.
  • openedx-events requires update for send_event_with_metadata.
  • edx-platform will require an update to send the db time from mongo. [Note: Robert started this locally.]
  • event-bus-kafka needs several changes:
    • Add ce_time, sourcelib, and minorversion to header.
    • Add new header items to logs, unless we get that for free. [Note: We do indeed get this for free]
    • Switch to call send_event_with_metadata for consumers.

Refactor AvroAttrsBridge into pre-serialization and post-deserialization parts

Break up the functionality in the AvroAttrsBridge to make responsibilities more clear

Acceptance Criteria:

  • Determine and implement refactor that replaces term “Bridge” (see notes)
  • Update docs (including ADRs?) to reflect updated names.
  • Update openedx-events with refactor
  • Update license-manager to use new code for producing
  • Update edx-arch-experiments to use new code for consuming

Original (private) Jira ticket: https://2u-internal.atlassian.net/browse/ARCHBOM-2102

[WH] Avoid adding UserPersonalData to the event bus

Some of the events specified in openedx-events contain PII. One clear example is the UserPersonalData class.

AC:

  • Find a way to go from an OpenEdxPublicSignal (which includes a UserPersonalData) to be serialized for the event bus without this data, and deserialized in a way that it can be sent using the same signal, but without the original personal data.
  • Include tests
  • Add documentation for consuming these events

[Consumer] Prevent event bus recursion

It is possible (though moderately unlikely) that an IDA could inadvertently be configured to both produce a signal to the event bus and also consume that signal from the event bus. This would result in any such event being reproduced and circulated indefinitely on a tight loop, causing quadratically increasing load and eventually an outage.

There are a couple of ways we could avoid this:

  1. When converting an event to a Django signal-send, mark it as having been consumed from the event bus, and do not allow such marked signal-sends to be produced back onto the event bus. This is the simplest option and would only require having a special keyword arg that won't collide with other kwargs on the signal-send.
    • We may eventually start attaching event envelope/header information to signal-sends (e.g. to preserve and convey timestamps) and the mere presence of this information would suffice as a "mark".
  2. Have openedx-events refuse to configure both a consumer and a producer for the same topic in the same IDA (raise exception on startup). This is a bigger hammer and may not be what we want.
  3. Record the IDA-of-origin on any OpenEdxPublicSignal send and copy that onto events and signal-sends when producing and consuming, then in the consumer drop any event that originated on the IDA on a previous iteration. Much more complicated, but would handle the (less likely) case of mutual recursion between 2+ IDAs.

Any such safeguard would not be able to detect anything that explicitly receives, recreates, and re-sends a signal, but that sort of thing is probably not worth worrying about.

[OEP-55 PILOT] catalog-info correctness setup

Description

The repo catalog-info must show correct/valid information about the project's current status, and the maintainers can find information on:
- Seeing what you own.
- Seeing what other people own.
- Seeing how repos relate to each other.

[Abstraction] Configuration-based teleportation of signals

Currently, each IDA that wants to publish events must add a @receiver-decorated function that listens for a signal and calls the event-bus producer (specifying a topic and key-field as well). It would probably be better to instead have openedx-events do this automatically. Perhaps we could have settings like the following:

EVENT_BUS_AUTOSEND = [
  {
    'signal': 'org.openedx.content_authoring.course.catalog_info.changed.v1',
    'topic': 'course-catalog-info-changed',
    'key_field': 'catalog_info.course_key',
  },
  ...
]
  • IDAs could still hardcode the relationship between signals and events when there's a more complicated relationship, but would not be required to for the simple, common case.
  • IDAs would have to specify openedx-events as an installed-app or otherwise allow it to perform some initialization at startup (so it could read the config and attach its own signal receivers).
  • Depending on the outcome of #86 the key-field may not be necessary to specify.

Marking events experimental

Someone (not me, but I am completely blanking on who at the moment 😞 ) brought up the idea of marking new events "experimental" for a certain period of time after initial introduction so that we have a little more flexibility to change things before we mark them as "supported" for long term usage. I thought this was a good idea, but I'm curious what others thought.

FYI: @mariajgrimaldi, @felipemontoya, @feanil, @robrap

[OEP 55 PILOT] Maintainership bots setup

Description

Bots that help with maintenance work must be correctly configured and working. Bots like:

  • Dependency bot running at least weekly
  • Auto branch deletion
  • Any other useful bot

[Abstraction] Configurable loading of Event Bus consumer implementation

We need a way to load event-bus consumer implementations based on configuration, so that IDAs aren't hardcoded to use one particular implementation. (See #87 for the producer side.)

There are a couple of ways we might go about this:

  • A. Define an abstract class in openedx-events, and load an implementing class based on Django settings, the same way we do with producers.
    • See commit message on main...timmc/load-consumer for open questions about what the interface might look like. (We also need to consider whether event-replay functionality would be included in this API.)
    • Here's what the implementation might look like in event-bus-kafka: openedx/event-bus-kafka@main...timmc/consumer-api
    • openedx-events could then be registered as a Django app, and offer a "consume events" management command that reads configuration and loads the appropriate implementation.
  • B. Just have every implementation make its own management command, requiring all of them to be registered in INSTALLED_APPS.
    • This moves the hardcoding from the IDA source code into the infrastructure source code.
    • Might be OK temporarily, but at some point we probably want community Helm charts or something and will not be happy with that hardcoding.
  • C. Are there other options? Should we support an entry point that isn't a management command?

ARCHBOM-2010: How does Event Bus interact with OpenedX Django Signals?

Context

The design intent behind eduNEXT/openedx-events was to be able to reuse the event definitions for both internal events (signals) and external events (message bus).

Currently, the plan seems to have all events be signals. Some of these signals would be received by a Kafka producer, which will then publish the data in the signal to the event bus. The Kafka consumer would then listen to the event and convert it back to a signal.

This would mean:

  • no event would ever just be a kafka event. For every Kafka event, there would be a signal event (not vice-versa).
  • Kafka event is only produced if a signal is send (by sender) and received (by Kafka Producer).

The definitions for the django signals are defined in signal.py. Specifically, django signals definition in encapsulated in OpenEdxPublicSignal class. OpenEdxPublicSignal is a django signal classs.

Questions

  1. How sure are we that we want a signal event for every Kafka event?

  2. Does OpenEdxPublicSignal actually need to be a signal? Even if we decide to start with Event Bus events always being sent downstream of the signal (for now), does it make sense to enforce this in the class design and leave no room for adjustments?
    This class overrides some signal methods to get users to use a special method.

    a. Is it possible for this class to just contain a signal class instead (when needed), or can the signal class not be dynamic?

  3. Could OpenEdxPublicSignal be responsible for sending both signal and Kafka event bus events?
    This complexity may not be worth it now without a strong need, but it could be noted as a possibility. For now, maybe this should be documented as a rejected alternative, simply because it isn’t yet required.

  4. Could we want the design of an event to look different when internal vs external? For example, how will we design these lifecycle events? See ARCHBOM-2008

[CloudEvents] CloudEvents for event bus events

Acceptance Criteria:

  • Implement and document (code comment or ADR referring to OEP) final CloudEvent headers.
    • ce_datacontenttype (content-type?)
    • ce_id
    • ce_source (don't include workers vs webapp if challenging)
    • sourcehost (extension)
    • ce_specversion
    • ce_type (Done)
    • minorversion (extension) (Deferred)
    • ce_time (Deferred)
  • Add logging of headers for producer and consumer error logging.

Notes:

From https://github.com/openedx/openedx-events/blob/main/docs/decisions/0005-external-event-schema-format.rst:

OEP-41 Asynchronous Server Event Message Format also dictates the use of the CloudEvents specification. Combined with this ADR, we would be required to adhere to the CloudEvents Avro Format. There may also be additional CloudEvent related work tied to a particular protocol binding, like the Kafka Protocol Binding for CloudEvents. This, however, is out of scope of this particular decision.

Our current approach has been to generally follow the CloudEvents spec as we understand it where and when we need to make a choice. However, we are not putting attention to ensuring that we have a complete or purely correct implementation. It is unclear how to do so, and what the benefits would be of such an effort at this time.

For current references, see:

The purpose of this issue is to communicate the current approach. If someone wants to put in more effort to review/implement any gaps around this, that help is welcome.

FYI: @ormsbee: Since you were the author of the OEP. Note: if you have no issues with this, we can close this issue.

Audit for timestamp sent on existing signals

Signals implemented and sent before timestamp was an available parameter are possibly not meeting the time requirements as documented in OEP-41.

This issue proposes a one-time audit and cataloging of which implementations should be updated. That work could potentially be separately ticketed. I know things will be missed with the current process, where I try to remember to ask others when they put events on the event bus. The XBLOCK_X related events are soon to be produced to the event bus, and I'm not clear if there is an appropriate timestamp to be used.

FYI: @ormsbee

[Event Bus] Implement happy-path for simple Studio publish events

This epic is part of the Event Bus work documented in openedx/platform-roadmap#28.

The epic covers the implementation of the happy-path for a new use case. The current plan is to implement the use case of course publishing from Studio that could be consumed by Discovery, potentially replacing a small piece of the Refresh Course Metadata batch job. For the purposes of this Epic, it may be enough to just consume and log the events in Discovery.

The epic will include any work to make the existing event bus boiler plate code more reusable across repos, rather than having it duplicated from the existing edx-platform experiments plugin and the existing license-manager code.

Milestone Details:

  • Title: [Event Bus] Implement happy-path
  • Description: See https://github.com/openedx/openedx-events/issues/63 for Epic details.
  • Due Date: TBD
    • Important: Date changes need to be fixed in all repos with this milestone defined.

Open issues/PRs with this milestone: https://github.com/pulls?q=is%3Aopen+milestone%3A%22%5BEvent+Bus%5D+Implement+happy-path%22

Better testing of event bus consumer API changes

I'm not clear if this is really an issue for the implementation repos (Kafka or Redis), or for openedx-event where the consumer API interface is defined. However, at some point a breaking change was made to this interface, and we did not have proper test coverage to prevent us from breaking (at least) one of the implementations.

  • Is this an issue with how we define the interface? We think an abstract class is used, but maybe not enough of the details make it to the interface?
  • Or is this just a test coverage issue?

This ticket is to understand what went wrong and how this could be prevented in the future. This may also include doc enhancements regarding the right way to create and test an implementation.

send_event loses observability into receiver exceptions

send_robust catches exceptions raised by receivers and returns them (which are then passed through send_event as well), but this means that errors don't make it into New Relic (or other telemetry). This is a general problem with Django, but it may be particularly troublesome in the context of signal teleportation on the event bus, as the receiver may begin failing (perhaps due to a new bug or a change in the data) and the errors would not raise alerts or be available for debugging. Being able to detect a broken producer/consumer pair and fix it will be important.

The solution here may be to set a custom attribute, call NR's notice_exception, or even ask NR to instrument send_robust (although that would only solve the issue for NR users).

Event design and naming how-to

We should consider adding a how-to for adding events, that may pull from and reference ADRs, but would be a clear how-to document.

One potentially missing detail, is whether all Data objects should have the suffix "Data"? See

class DiscussionTopicContext:
for one example that doesn't match this convention. Separate and related, do we want linting to help enforce any conventions?

Do we want to document known subdomains? (I have additional questions about this, but better left to a separate issue.)

[Data] Create new CMS_COURSE_PUBLISHED signal to represent courses being updated in Studio

  • Create a CMS_COURSE_PUBLISHED (name open to discussion) OpenEdxPublicSignal in openedx-events with the fields listed below (with appropriate data types)
  • Get TNL and tCRIL feedback on the event design and location within openedx-events

Fields:
blocks_url
effort
end
enrollment_start
enrollment_end
id
media: { banner_image: {uri, uri_absolute}, course_image: {uri}, course_video: {uri}, image: {raw, small, large} }
name
number
org
short_description
start
start_display
start_type
pacing
mobile_available
hidden
invitation_only
course_id

Note: CMS_COURSE_PUBLISHED is just a suggestion for a name and can be improved on. We should make sure we are consistent with "CMS" vs "Studio" nomenclature.

Open Questions:
Where in the openedx-events module structure should this new event live? @ormsbee does tCRIL have a strong opinion on this?

[Event Bus] Future

This epic is part of the Event Bus work documented in openedx/platform-roadmap#28.

This epic can be used for future event bus tasks that don't yet fall into an existing planned epic/milestone. Tasks can be moved from this milestone to a planned milestone as-needed.

Milestone Details:

  • Title: [Event Bus] Future
  • Description: See https://github.com/openedx/openedx-events/issues/74 for Epic details.
  • Due Date: n/a

Open issues/PRs with this milestone: https://github.com/pulls?q=is%3Aopen+milestone%3A%22%5BEvent+Bus%5D+Future%22

Idea: Tracking Log OpenEdxPublicSignal

This is a placeholder issue to start discussion on this topic. There is a desire to create a single “tracking log” OpenEdxPublicSignal that can be used for all tracking log events to facilitate event handling and transformation. Specifically openedx/wg-data#28 calls for this, but several other xAPI use cases are in various levels of ideation.

The general idea is that once #210 is done we would like to add this new signal, and introduce a few other changes as outlined in the epic above to support configurable an event logs -> xAPI pipeline (as well as other consumers) that operators can tailor to their needs. Given the volume of tracking logs we're hoping that we can filter logs by event name on the producer side as part of this work.

[Publishing] Serializer errors with optional datetime fields

When using a data class with an optional datetime field, the AvroSignalSerializer will error if the field value is not present or is None (even though it's supposed to be optional).

The following test can be used to recreate the issue:

class AttrsWithDefaultDate:
    birthday = attr.ib(type=datetime, default=None)

def test_serialization_of_optional_date_field(self):
        SIGNAL = create_simple_signal({"test_data": AttrsWithDefaultDate})
        serializer = AvroSignalSerializer(SIGNAL)
        test_data = {"test_data": AttrsWithDefaultDate()}
        serialize_event_data_to_bytes(test_data, SIGNAL)
        self.assertNull(test_data["test_data"]["birthday"])

[Abstraction] Determine a dependency versioning strategy for event-bus implementations

The edx_event_bus_kafka dependency will be specified and pinned in a file outside of each IDA that uses it, since openedx-events will load the implementation by looking up a module reference in the IDA's config. This means that dependency version changes on the library will not be versioned with the IDA (i.e. in the requirements/base.txt files) and we'll need some sort of expand-contract approach to API changes.

For example, we might need to do something like this:

  1. Add a new KafkaProducer class in event-bus-kafka and delegate the existing send_to_event_bus to use it
  2. Switch the producer code in Studio to use the new class
  3. Delete the send_to_event_bus function from event-bus-kafka

Every master-branch deployer would need to do this in lockstep. (Maybe that's just 2U?) For Open edX releases we might be able to get away with just saying "use the release tag".

Acceptance Criteria

  • Design a strategy that will work for deployers in general
  • Document how-to for these upgrades in appropriate places.

Ability to have private event schemas

Hello!

I have a non-open source project within the edX ecosystem that I would like to use kafka/the event bus with; however, I would ideally not need to have to define events in an open source repo for a private project (not that it's secret per se, but just that it would be mysterious).

Curious what kinds of thoughts you have on the matter!

Thanks,
Chris

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.