Forum v2 compatibility on Open edX master branch

While deploying the new forum v2 app in production, we realised that it was necessary to configure forum v2 even if the forum v2 toggle was not enabled.

Even if the forum v2 toggle is not enabled, edx-platform will make a call to the forum v2 API in some edge cases. That’s because edx-platform needs to determine whether it should use forum v2 or cs_comments_service, based on the value of some course waffle flag. In order to access the course wafffle flag, we need to determine the course ID of the current HTTP request. In some requests, the course ID is not available: only the thread ID or the comment ID is. Thus, edx-platform needs to fetch the course ID that is associated to the thread or comment. That information is stored either in MySQL or in MongoDB. Thus, edx-platform needs to call the forum v2 API.

This is an unfortunate situation, but we did not find any alternative approach…

As a consequence, the forum v2 app needs to have accurate MongoDB configuration settings even if you don’t use forum v2. In a Tutor installation, these settings are set to the right values. In other environments, the following Django settings must be set::

# Name of the MongoDB database in which forum data is stored
FORUM_MONGODB_DATABASE = "cs_comments_service"

# This setting will be passed to the MongoDB client constructor as follows:
# pymongo.MongoClient(**FORUM_MONGODB_CLIENT_PARAMETERS)
# Documentation: https://pymongo.readthedocs.io/en/4.4.0/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient
FORUM_MONGODB_CLIENT_PARAMETERS = {"host": "mongodb"}

Let me know if you have any questions.

What happens when these settings don’t exist? Is there an error message or behavior we should anticipate?

@pdpinch: it’ll throw an exception:

File "/edx/app/edxapp/edx-platform/openedx/core/djangoapps/django_comment_common/comment_client/thread.py", line 176, in _retrieve
    course_id = forum_api.get_course_id_by_thread(self.id)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Here’s the full stracktrace on 2U:

Traceback (most recent call last):
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/contextlib.py", line 81, in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/ddtrace/contrib/trace_utils.py", line 336, in wrapper
    return func(mod, pin, wrapped, instance, args, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/ddtrace/contrib/internal/django/patch.py", line 333, in wrapped
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/django/views/decorators/csrf.py", line 56, in wrapper_view
    return view_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/ddtrace/contrib/trace_utils.py", line 336, in wrapper
    return func(mod, pin, wrapped, instance, args, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/ddtrace/contrib/internal/django/patch.py", line 333, in wrapped
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/edx-platform/lms/djangoapps/discussion/rest_api/views.py", line 964, in list
    return self.list_by_thread(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/edx-platform/lms/djangoapps/discussion/rest_api/views.py", line 973, in list_by_thread
    return get_comment_list(
           ^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/edx-platform/lms/djangoapps/discussion/rest_api/api.py", line 1212, in get_comment_list
    cc_thread, context = _get_thread_and_context(
                         ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/edx-platform/lms/djangoapps/discussion/rest_api/api.py", line 216, in _get_thread_and_context
    cc_thread = Thread(id=thread_id).retrieve(course_id=course_id, **retrieve_kwargs)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/edx-platform/openedx/core/djangoapps/django_comment_common/comment_client/models.py", line 69, in retrieve
    self._retrieve(*args, **kwargs)
  File "/edx/app/edxapp/edx-platform/openedx/core/djangoapps/django_comment_common/comment_client/thread.py", line 176, in _retrieve
    course_id = forum_api.get_course_id_by_thread(self.id)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/forum/api/threads.py", line 402, in get_course_id_by_thread
    MongoBackend.get_course_id_by_thread_id(thread_id)
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/forum/backends/mongodb/api.py", line 1592, in get_course_id_by_thread_id
    thread = CommentThread().get(thread_id)
             ^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/forum/backends/mongodb/contents.py", line 26, in __init__
    self.create_indexes()
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/forum/backends/mongodb/contents.py", line 32, in create_indexes
    self._collection.create_index(
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/pymongo/collection.py", line 2071, in create_index
    return self.__create_indexes([index], session, **cmd_options)[0]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/pymongo/_csot.py", line 106, in csot_wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/pymongo/collection.py", line 1922, in __create_indexes
    with self._socket_for_writes(session) as sock_info:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/pymongo/collection.py", line 249, in _socket_for_writes
    return self.__database.client._socket_for_writes(session)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/pymongo/mongo_client.py", line 1278, in _socket_for_writes
    server = self._select_server(writable_server_selector, session)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/pymongo/mongo_client.py", line 1268, in _select_server
    server = topology.select_server(server_selector)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/pymongo/topology.py", line 270, in select_server
    """Like select_servers, but choose a random server if several match."""
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/ddtrace/contrib/internal/pymongo/client.py", line 102, in _trace_topology_select_server
    server = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/pymongo/topology.py", line 271, in select_server
    server = self._select_server(selector, server_selection_timeout, address)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/edx/app/edxapp/venvs/edxapp/lib/python3.11/site-packages/pymongo/topology.py", line 260, in _select_server
    servers = self.select_servers(selector, server_selection_timeout, address)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pymongo.errors.ServerSelectionTimeoutError: mongodb:27017: [Errno -3] Temporary failure in name resolution, Timeout: 30s, Topology Description: <TopologyDescription id: 675091e2f60ec73d0474c189, topology_type: Unknown, servers: [<ServerDescription ('mongodb', 27017) server_type: Unknown, rtt: None, error=AutoReconnect('mongodb:27017: [Errno -3] Temporary failure in name resolution')>]>