Skip to content

Commit

Permalink
Add importcontent --fail-on-error option
Browse files Browse the repository at this point in the history
Normally importcontent carries on when it can't import a file and prints
a warning on the number of skipped files. That makes it unreliable when
trying to ensure a fully provisioned device. Add the --fail-on-error
option to make import errors fatal.

Fixes: learningequality#9258
  • Loading branch information
dbnicholson authored and AtKristijan committed Apr 19, 2022
1 parent c5e2361 commit 2077310
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 1 deletion.
19 changes: 18 additions & 1 deletion kolibri/core/content/management/commands/importcontent.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ def add_arguments(self, parser):
help="Import all updated content after a channel version upgrade",
)

parser.add_argument(
"--fail-on-error",
action="store_true",
default=False,
dest="fail_on_error",
help="Raise an error when a file has failed to be imported",
)

# to implement these two groups of commands and their corresponding
# arguments, we'll need argparse.subparsers.
subparsers = parser.add_subparsers(
Expand Down Expand Up @@ -160,6 +168,7 @@ def download_content(
peer_id=None,
renderable_only=True,
import_updates=False,
fail_on_error=False,
):
self._transfer(
DOWNLOAD_METHOD,
Expand All @@ -170,6 +179,7 @@ def download_content(
peer_id=peer_id,
renderable_only=renderable_only,
import_updates=import_updates,
fail_on_error=fail_on_error,
)

def copy_content(
Expand All @@ -181,6 +191,7 @@ def copy_content(
exclude_node_ids=None,
renderable_only=True,
import_updates=False,
fail_on_error=False,
):
self._transfer(
COPY_METHOD,
Expand All @@ -191,6 +202,7 @@ def copy_content(
exclude_node_ids=exclude_node_ids,
renderable_only=renderable_only,
import_updates=import_updates,
fail_on_error=fail_on_error,
)

def _transfer( # noqa: max-complexity=16
Expand All @@ -205,6 +217,7 @@ def _transfer( # noqa: max-complexity=16
peer_id=None,
renderable_only=True,
import_updates=False,
fail_on_error=False,
):
try:
if not import_updates:
Expand Down Expand Up @@ -426,8 +439,10 @@ def _transfer( # noqa: max-complexity=16
logger.error(
"An error occurred during content import: {}".format(e)
)

if (
isinstance(e, requests.exceptions.HTTPError)
not fail_on_error
and isinstance(e, requests.exceptions.HTTPError)
and e.response.status_code == 404
) or (isinstance(e, OSError) and e.errno == 2):
# Continue file import when the current file is not found from the source and is skipped.
Expand Down Expand Up @@ -537,6 +552,7 @@ def handle_async(self, *args, **options):
peer_id=options["peer_id"],
renderable_only=options["renderable_only"],
import_updates=options["import_updates"],
fail_on_error=options["fail_on_error"],
)
elif options["command"] == "disk":
self.copy_content(
Expand All @@ -547,6 +563,7 @@ def handle_async(self, *args, **options):
exclude_node_ids=options["exclude_node_ids"],
renderable_only=options["renderable_only"],
import_updates=options["import_updates"],
fail_on_error=options["fail_on_error"],
)
else:
self._parser.print_help()
Expand Down
57 changes: 57 additions & 0 deletions kolibri/core/content/test/test_import_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,63 @@ def test_remote_import_file_compressed_on_gcs(
public=False,
)

@patch("kolibri.core.content.management.commands.importcontent.logger.warning")
@patch(
"kolibri.core.content.management.commands.importcontent.paths.get_content_storage_file_path"
)
def test_remote_import_fail_on_error(
self,
path_mock,
logger_mock,
annotation_mock,
get_import_export_mock,
channel_list_status_mock,
):
fd1, local_dest_path_1 = tempfile.mkstemp()
fd2, local_dest_path_2 = tempfile.mkstemp()
fd3, local_dest_path_3 = tempfile.mkstemp()
os.close(fd1)
os.close(fd2)
os.close(fd3)
path_mock.side_effect = [
local_dest_path_1,
local_dest_path_2,
local_dest_path_3,
]
ContentNode.objects.filter(pk="2b6926ed22025518a8b9da91745b51d3").update(
available=False
)
LocalFile.objects.filter(
files__contentnode__pk="2b6926ed22025518a8b9da91745b51d3"
).update(file_size=1, available=False)
get_import_export_mock.return_value = (
1,
list(
LocalFile.objects.filter(
files__contentnode__pk="2b6926ed22025518a8b9da91745b51d3"
).values("id", "file_size", "extension")
),
10,
)

node_id = ["2b6926ed22025518a8b9da91745b51d3"]
with self.assertRaises(HTTPError):
call_command(
"importcontent",
"network",
self.the_channel_id,
node_ids=node_id,
renderable_only=False,
fail_on_error=True,
)
annotation_mock.set_content_visibility.assert_called_with(
self.the_channel_id,
[],
node_ids=node_id,
exclude_node_ids=None,
public=False,
)


@override_option("Paths", "CONTENT_DIR", tempfile.mkdtemp())
class ExportChannelTestCase(TestCase):
Expand Down

0 comments on commit 2077310

Please sign in to comment.