Skip to content

Commit 41377b9

Browse files
committed
Add progress_callback to create.
1 parent d57f81c commit 41377b9

2 files changed

Lines changed: 40 additions & 4 deletions

File tree

maas/client/viscera/boot_resources.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import hashlib
1010
import httplib2
1111
import io
12+
from typing import Callable
1213

1314
from . import (
1415
check,
@@ -117,7 +118,7 @@ def create(
117118
cls, name: str, architecture: str, content: io.IOBase, *,
118119
title: str=None,
119120
filetype: BootResourceFiletype=BootResourceFiletype.TGZ,
120-
chunk_size=(1 << 22)):
121+
chunk_size=(1 << 22), progress_callback: Callable=None):
121122
"""Create a `BootResource`.
122123
123124
Creates an uploaded boot resource with `content`. The `content` is
@@ -139,6 +140,11 @@ def create(
139140
:param chunk_size: Size in bytes to upload to MAAS in chunks.
140141
(Default is 4 MiB).
141142
:type chunk_size: `int`
143+
:param progress_callback: Called to inform the current progress of the
144+
upload. One argument is passed with the progress as a precentage.
145+
If the resource was already complete and no content
146+
needed to be uploaded then this callback will never be called.
147+
:type progress_callback: Callable
142148
:returns: Create boot resource.
143149
:rtype: `BootResource`.
144150
"""
@@ -178,6 +184,11 @@ def create(
178184
elif chunk_size <= 0:
179185
raise ValueError(
180186
"chunk_size must be greater than 0, not %d" % chunk_size)
187+
if (progress_callback is not None and
188+
not isinstance(progress_callback, Callable)):
189+
raise TypeError(
190+
"progress_callback must be a Callable, not %s" % (
191+
type(progress_callback).__name__))
181192

182193
size, sha256 = calc_size_and_sha265(content, chunk_size)
183194
resource = cls._object(cls._handler.create(
@@ -191,12 +202,13 @@ def create(
191202
return resource
192203
else:
193204
# Upload in chunks and reload boot resource.
194-
cls._upload_chunks(rfile, content, chunk_size)
205+
cls._upload_chunks(rfile, content, chunk_size, progress_callback)
195206
return cls._object.read(resource.id)
196207

197208
@classmethod
198209
def _upload_chunks(
199-
cls, rfile: BootResourceFile, content: io.IOBase, chunk_size: int):
210+
cls, rfile: BootResourceFile, content: io.IOBase, chunk_size: int,
211+
progress_callback: Callable=None):
200212
"""Upload the `content` to `rfile` in chunks using `chunk_size`."""
201213
content.seek(0, io.SEEK_SET)
202214
upload_uri = "%s%s" % (
@@ -207,6 +219,8 @@ def _upload_chunks(
207219
length = len(buf)
208220
if length > 0:
209221
cls._put_chunk(upload_uri, buf)
222+
if progress_callback is not None:
223+
progress_callback(length / rfile.size)
210224
if length != chunk_size:
211225
break
212226

maas/client/viscera/tests/test_boot_resources.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,16 @@ def test__create_raises_ValueError_when_chunk_size_is_less_than_zero(self):
276276
self.assertEquals(
277277
"chunk_size must be greater than 0, not -1", str(error))
278278

279+
def test__create_raises_TypeError_when_progress_callback_no_callable(self):
280+
BootResources = make_origin().BootResources
281+
282+
buf = io.BytesIO(b"")
283+
error = self.assertRaises(
284+
TypeError, BootResources.create,
285+
"os/release", "arch/subarch", buf, progress_callback='')
286+
self.assertEquals(
287+
"progress_callback must be a Callable, not str", str(error))
288+
279289
def test__create_calls_create_on_handler_does_nothing_if_complete(self):
280290
resource_id = random.randint(0, 100)
281291
name = "%s/%s" % (
@@ -418,9 +428,14 @@ def test__create_uploads_in_chunks_and_reloads_resource(self):
418428
httplib2.Response({"status": 200}), b"")
419429
self.patch(boot_resources.httplib2, "Http", http)
420430

431+
# Progress handler called on each chunk.
432+
progress_handler = MagicMock()
433+
434+
# Create and upload the resource.
421435
resource = BootResources.create(
422436
name, architecture, buf,
423-
title=title, filetype=filetype, chunk_size=chunk_size)
437+
title=title, filetype=filetype, chunk_size=chunk_size,
438+
progress_callback=progress_handler)
424439

425440
# Check that returned resource is correct and updated.
426441
self.assertThat(resource, MatchesStructure.byEquality(
@@ -445,6 +460,13 @@ def test__create_uploads_in_chunks_and_reloads_resource(self):
445460
]
446461
self.assertEquals(calls, http.return_value.request.call_args_list)
447462

463+
# Check that progress handler was called on each chunk.
464+
calls = [
465+
call(len(data[0 + i:chunk_size + i]) / len(data))
466+
for i in range(0, len(data), chunk_size)
467+
]
468+
self.assertEquals(calls, progress_handler.call_args_list)
469+
448470
def test__create_raises_CallError_on_chunk_upload_failure(self):
449471
resource_id = random.randint(0, 100)
450472
name = "%s/%s" % (

0 commit comments

Comments
 (0)