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

Automatic content syncing: preferred instance tracking and fixes #11083

Merged
merged 31 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b25c4ac
Consolidate api resources into one set
bjester Aug 8, 2023
a40372f
Add new source instance ID field for preferred download sourcing
bjester Aug 8, 2023
19bbe0a
Allow setting of source instance ID in API with tests
bjester Aug 8, 2023
ea0429f
Pass along instance id when initiating a download
bjester Aug 8, 2023
a8b3dfb
Handle preferred instances when importing metadata
bjester Aug 8, 2023
b6fc377
Upgrade le_utils and incorporate well known Studio UUID
bjester Aug 8, 2023
d5b9e55
Add new location_type field, switch over to it
bjester Aug 8, 2023
7c438f4
Update resource import to use preferred location
bjester Aug 8, 2023
f9f9bb6
use studio id string for now
bjester Aug 8, 2023
dd9be51
Filter network locations when on metered connection with disabled
bjester Aug 8, 2023
e4c1c09
Special handling for reserved locations
bjester Aug 8, 2023
7abe48a
Rename existing external download for disambiguation
bjester Aug 9, 2023
39edad2
Defensiveness on UUID vs str
bjester Aug 9, 2023
db1deca
Allow detail fetching using instance_id
bjester Aug 9, 2023
f8b8fee
Log routing errors and dedupe router.replace calls
bjester Aug 9, 2023
323b25a
Integrate allow learner downloads
bjester Aug 9, 2023
b8bce32
More integration with device settings
bjester Aug 9, 2023
c5d14ae
Fixes for total size aggregation and tests
bjester Aug 9, 2023
dae1282
Content download from studio testing
bjester Aug 9, 2023
bee839a
Override content settings function for checking metered status
bjester Aug 9, 2023
140fa63
Adjust testing check
bjester Aug 9, 2023
b65cd2e
Frontend test fixes
bjester Aug 9, 2023
902f377
Mock that the netloc is not reserved
bjester Aug 9, 2023
4bf143d
Add testing flag to core settings, combine total size annotation, bre…
bjester Aug 10, 2023
1ba3cca
Different approach for inline migration
bjester Aug 10, 2023
b9e3098
Return promise since .catch is applied to return value
bjester Aug 10, 2023
38894ce
Increase diff to better identify issue with flaky tests
bjester Aug 10, 2023
81148be
Add super setup call for safety
bjester Aug 10, 2023
c62b880
Set maxDiff as class prop
bjester Aug 11, 2023
754ba37
Use generator classes, fresh tests, and fixes
bjester Aug 15, 2023
6bde9ea
Add warning about 0 count
bjester Aug 15, 2023
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
2 changes: 1 addition & 1 deletion kolibri/core/content/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1333,7 +1333,7 @@ def consolidate(self, items, queryset):


class ContentRequestViewset(ReadOnlyValuesViewset, CreateModelMixin):
serializer_class = serializers.ContentDownloadRequestSeralizer
serializer_class = serializers.ContentDownloadRequestSerializer

pagination_class = OptionalPageNumberPagination

Expand Down
13 changes: 5 additions & 8 deletions kolibri/core/content/kolibri_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,14 @@ def handle_initial(self, context):
"""
:type context: morango.sync.context.LocalSessionContext
"""
from kolibri.core.device.utils import get_device_setting
from kolibri.core.device.utils import device_provisioned
from kolibri.core.content.utils.settings import automatic_download_enabled

# only needs to synchronize requests when on receiving end of a sync
self._assert(context.is_receiver)

# either the device setting is enabled, or we haven't provisioned yet. If the device isn't
# provisioned, we allow this because the default will be True after provisioning
self._assert(
get_device_setting(
"enable_automatic_download", default=not device_provisioned()
)
)
self._assert(automatic_download_enabled())

dataset_id = get_dataset_id(context)
logger.info(
Expand Down Expand Up @@ -69,8 +64,10 @@ def post_transfer(
Processes content import using the post_transfer hook, outside of the sync process, but
between push and pulls (since this processes when receiving)
"""
from kolibri.core.content.utils.settings import automatic_download_enabled

# only process upon receiving
if not context.is_receiver:
if not context.is_receiver or not automatic_download_enabled():
return

# process metadata import for new requests without metadata
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2023-08-08 13:46
from __future__ import unicode_literals

import morango.models.fields.uuids
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("content", "0033_content_requests"),
]

operations = [
migrations.AddField(
model_name="contentrequest",
name="source_instance_id",
field=morango.models.fields.uuids.UUIDField(blank=True, null=True),
),
]
2 changes: 2 additions & 0 deletions kolibri/core/content/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,8 @@ class ContentRequest(models.Model):
source_model = models.CharField(max_length=40)
# the source model's PK, could be the user's ID
source_id = UUIDField()
# the instance ID of the preferred device from which to source/download the content
source_instance_id = UUIDField(null=True, blank=True)

requested_at = DateTimeTzField(default=local_now)

Expand Down
11 changes: 4 additions & 7 deletions kolibri/core/content/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,18 +265,14 @@ class ContentDownloadRequestMetadataSerializer(serializers.Serializer):
)


class ContentDownloadRequestSeralizer(serializers.ModelSerializer):

class ContentDownloadRequestSerializer(serializers.ModelSerializer):
source_instance_id = serializers.UUIDField(required=False, allow_null=True)
metadata = ContentDownloadRequestMetadataSerializer()

class Meta:

model = ContentDownloadRequest
fields = (
"id",
"contentnode_id",
"metadata",
)
fields = ("id", "contentnode_id", "metadata", "source_instance_id")

def create(self, validated_data):
# if there is an existing deletion request, delete the deletion request
Expand Down Expand Up @@ -307,6 +303,7 @@ def create(self, validated_data):
content_request = ContentDownloadRequest.build_for_user(user)
content_request.metadata = validated_data["metadata"]
content_request.contentnode_id = validated_data["contentnode_id"]
content_request.source_instance_id = validated_data.get("source_instance_id")

content_request.save()
automatic_resource_import.enqueue_if_not()
Expand Down
32 changes: 24 additions & 8 deletions kolibri/core/content/tasks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import requests
from django.core.exceptions import ValidationError
from django.core.management import call_command
from rest_framework import serializers
from six import with_metaclass
Expand All @@ -19,8 +20,8 @@
RemoteChannelResourceImportManager,
)
from kolibri.core.content.utils.resource_import import RemoteChannelUpdateManager
from kolibri.core.content.utils.settings import automatic_download_enabled
from kolibri.core.content.utils.upgrade import diff_stats
from kolibri.core.device.utils import get_device_setting
from kolibri.core.discovery.models import NetworkLocation
from kolibri.core.discovery.utils.network.client import NetworkClient
from kolibri.core.discovery.utils.network.errors import IncompatibleVersionError
Expand Down Expand Up @@ -343,7 +344,16 @@ def remoteresourceimport(
import_manager.run()


class AutomaticDownloadValidator(JobValidator):
def validate(self, data):
job_data = super(AutomaticDownloadValidator, self).validate(data)
if not automatic_download_enabled():
raise ValidationError("Automatic download is not enabled")
return job_data


@register_task(
validator=AutomaticDownloadValidator,
queue=QUEUE,
long_running=True,
status_fn=get_status,
Expand All @@ -352,6 +362,8 @@ def automatic_resource_import():
"""
Processes content download and removal requests
"""
if not automatic_download_enabled():
return
process_content_requests()


Expand All @@ -366,16 +378,20 @@ def automatic_synchronize_content_requests_and_import():
- Enqueues the automatic_resource_import task after synchronizing content requests.
"""
# A safety check to see if the device settings are changed already?
if get_device_setting("enable_automatic_download", default=False) is True:
if not automatic_download_enabled():
return
else:
dataset_ids = FacilityDataset.objects.values_list("id", flat=True)

# Synchronize content requests for each dataset
for dataset_id in dataset_ids:
synchronize_content_requests(dataset_id, None)
dataset_ids = FacilityDataset.objects.values_list("id", flat=True)

# Synchronize content requests for each dataset
for dataset_id in dataset_ids:
# in case it takes a long time to synchronize content requests for a dataset, we want to
# check if the device settings are changed
if not automatic_download_enabled():
return
synchronize_content_requests(dataset_id, None)

automatic_resource_import.enqueue_if_not()
automatic_resource_import.enqueue_if_not()


class ExportChannelResourcesValidator(LocalMixin, ChannelResourcesValidator):
Expand Down
8 changes: 8 additions & 0 deletions kolibri/core/content/test/test_content_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,10 @@ class ContentNodeAPITestCase(ContentNodeAPIBase, APITestCase):
Testcase for content API methods
"""

def setUp(self):
super(ContentNodeAPITestCase, self).setUp()
self.maxDiff = 10000
bjester marked this conversation as resolved.
Show resolved Hide resolved

def test_prerequisite_for_filter(self):
c1_id = content.ContentNode.objects.get(title="c1").id
response = self.client.get(
Expand Down Expand Up @@ -1839,6 +1843,10 @@ def tearDown(self):


class ProxyContentMetadataTestCase(ContentNodeAPIBase, LiveServerTestCase):
def setUp(self):
super(ProxyContentMetadataTestCase, self).setUp()
self.maxDiff = 10000

@property
def baseurl(self):
return self.live_server_url
Expand Down
Loading
Loading