Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assignment handling within single-user syncing #8219

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
7 changes: 7 additions & 0 deletions kolibri/core/auth/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ class KolibriAuthConfig(AppConfig):
def ready(self):
from .signals import cascade_delete_membership # noqa: F401
from .signals import cascade_delete_user # noqa: F401

from kolibri.core.auth.sync_event_hook_utils import (
register_sync_event_handlers,
) # noqa: F401
from morango.api.viewsets import session_controller # noqa: F401

register_sync_event_handlers(session_controller)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure whether this was the best place to put this, but... I couldn't think of a better place. Suggestions welcome, or maybe this is ok.

29 changes: 29 additions & 0 deletions kolibri/core/auth/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from kolibri.plugins.hooks import define_hook
from kolibri.plugins.hooks import KolibriHook


@define_hook
class FacilityDataSyncHook(KolibriHook):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be separated out into a pre-hook and post-hook, which would then allow each method to be setup with an @abc.abstractmethod decorator, which then gives a clear requirement that the method be instantiated.

"""
A hook to allow plugins to register callbacks for sync events they're interested in.
"""

def pre_transfer(
self,
dataset_id,
local_is_single_user,
remote_is_single_user,
single_user_id,
context,
):
pass

def post_transfer(
self,
dataset_id,
local_is_single_user,
remote_is_single_user,
single_user_id,
context,
):
pass
19 changes: 13 additions & 6 deletions kolibri/core/auth/management/commands/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
from kolibri.core.auth.management.utils import get_facility
from kolibri.core.auth.management.utils import run_once
from kolibri.core.auth.models import dataset_cache
from kolibri.core.auth.sync_event_hook_utils import register_sync_event_handlers
from kolibri.core.logger.utils.data import bytes_for_humans
from kolibri.core.tasks.exceptions import UserCancelledError
from kolibri.core.tasks.management.commands.base import AsyncCommand
from kolibri.core.utils.lock import db_lock
from kolibri.utils import conf


DATA_PORTAL_SYNCING_BASE_URL = conf.OPTIONS["Urls"]["DATA_PORTAL_SYNCING_BASE_URL"]
TRANSFER_MESSAGE = "{records_transferred}/{records_total}, {transfer_total}"

Expand Down Expand Up @@ -218,6 +220,8 @@ def handle_async(self, *args, **options): # noqa C901
client_cert, server_cert, chunk_size=chunk_size
)

register_sync_event_handlers(sync_session_client.controller)

try:
# pull from server
if not no_pull:
Expand Down Expand Up @@ -407,7 +411,7 @@ def _handle_push(

# allow server timeout since remotely integrating data can take a while and the request
# could timeout. In that case, we'll assume everything is good.
sync_client.finalize(allow_server_timeout=True)
sync_client.finalize()

def _update_all_progress(self, progress_fraction, progress):
"""
Expand Down Expand Up @@ -475,11 +479,14 @@ def handler(transfer_session):
"""
:type transfer_session: morango.models.core.TransferSession
"""
progress = (
100
* transfer_session.records_transferred
/ float(transfer_session.records_total)
)
try:
progress = (
100
* transfer_session.records_transferred
/ float(transfer_session.records_total)
)
except ZeroDivisionError:
progress = 100
rtibbles marked this conversation as resolved.
Show resolved Hide resolved
tracker.update_progress(
increment=math.ceil(progress - tracker.progress),
message=stats_msg(transfer_session),
Expand Down
69 changes: 69 additions & 0 deletions kolibri/core/auth/sync_event_hook_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import json

from morango.sync.context import LocalSessionContext

from kolibri.core.auth.constants.morango_sync import ScopeDefinitions
from kolibri.core.auth.hooks import FacilityDataSyncHook


def _get_our_cert(context):
ss = context.sync_session
return ss.server_certificate if ss.is_server else ss.client_certificate


def _get_their_cert(context):
ss = context.sync_session
return ss.client_certificate if ss.is_server else ss.server_certificate


def _this_side_using_single_user_cert(context):
return _get_our_cert(context).scope_definition_id == ScopeDefinitions.SINGLE_USER


def _other_side_using_single_user_cert(context):
return _get_their_cert(context).scope_definition_id == ScopeDefinitions.SINGLE_USER


def _get_user_id_for_single_user_sync(context):
if _other_side_using_single_user_cert(context):
cert = _get_their_cert(context)
elif _this_side_using_single_user_cert(context):
cert = _get_our_cert(context)
else:
return None
return json.loads(cert.scope_params)["user_id"]


def _extract_kwargs_from_context(context):
return {
"dataset_id": _get_our_cert(context).get_root().id,
"local_is_single_user": _this_side_using_single_user_cert(context),
"remote_is_single_user": _other_side_using_single_user_cert(context),
"single_user_id": _get_user_id_for_single_user_sync(context),
"context": context,
}


def _pre_transfer_handler(context):
assert context is not None

kwargs = _extract_kwargs_from_context(context)

if isinstance(context, LocalSessionContext):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to avoid having the hooks be called twice, once for local and once for remote context.

for hook in FacilityDataSyncHook.registered_hooks:
hook.pre_transfer(**kwargs)


def _post_transfer_handler(context):
assert context is not None

kwargs = _extract_kwargs_from_context(context)

if isinstance(context, LocalSessionContext):
for hook in FacilityDataSyncHook.registered_hooks:
hook.post_transfer(**kwargs)


def register_sync_event_handlers(session_controller):
session_controller.signals.initializing.completed.connect(_pre_transfer_handler)
session_controller.signals.cleanup.completed.connect(_post_transfer_handler)
2 changes: 1 addition & 1 deletion kolibri/core/auth/test/sync_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def manage(self, *args):

def create_model(self, model, **kwargs):
kwarg_text = ",".join(
'{key}=\\"{value}\\"'.format(key=key, value=value)
"{key}={value}".format(key=key, value=repr(value))
for key, value in kwargs.items()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was getting some format issues in the piping -- this helped.

)
self.pipe_shell(
Expand Down
Loading