Skip to content

Commit b0f2183

Browse files
erikwebbtseaver
authored andcommitted
Update Blob.update_storage_class to support rewrite tokens (#6527)
1 parent d7ed6ee commit b0f2183

3 files changed

Lines changed: 83 additions & 22 deletions

File tree

packages/google-cloud-storage/google/cloud/storage/blob.py

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,7 +1512,9 @@ def rewrite(self, source, token=None, client=None):
15121512
return api_response["rewriteToken"], rewritten, size
15131513

15141514
def update_storage_class(self, new_class, client=None):
1515-
"""Update blob's storage class via a rewrite-in-place.
1515+
"""Update blob's storage class via a rewrite-in-place. This helper will
1516+
wait for the rewrite to complete before returning, so it may take some
1517+
time for large files.
15161518
15171519
See
15181520
https://cloud.google.com/storage/docs/per-object-storage-class
@@ -1530,25 +1532,13 @@ def update_storage_class(self, new_class, client=None):
15301532
if new_class not in self._STORAGE_CLASSES:
15311533
raise ValueError("Invalid storage class: %s" % (new_class,))
15321534

1533-
client = self._require_client(client)
1534-
1535-
query_params = {}
1536-
1537-
if self.user_project is not None:
1538-
query_params["userProject"] = self.user_project
1539-
1540-
headers = _get_encryption_headers(self._encryption_key)
1541-
headers.update(_get_encryption_headers(self._encryption_key, source=True))
1535+
# Update current blob's storage class prior to rewrite
1536+
self._patch_property('storageClass', new_class)
15421537

1543-
api_response = client._connection.api_request(
1544-
method="POST",
1545-
path=self.path + "/rewriteTo" + self.path,
1546-
query_params=query_params,
1547-
data={"storageClass": new_class},
1548-
headers=headers,
1549-
_target_object=self,
1550-
)
1551-
self._set_properties(api_response["resource"])
1538+
# Execute consecutive rewrite operations until operation is done
1539+
token, _, _ = self.rewrite(self)
1540+
while token is not None:
1541+
token, _, _ = self.rewrite(self, token=token)
15521542

15531543
cache_control = _scalar_property("cacheControl")
15541544
"""HTTP 'Cache-Control' header for this object.
@@ -1815,7 +1805,7 @@ def kms_key_name(self):
18151805
This can only be set at blob / object **creation** time. If you'd
18161806
like to change the storage class **after** the blob / object already
18171807
exists in a bucket, call :meth:`update_storage_class` (which uses
1818-
the "storage.objects.rewrite" method).
1808+
:meth:`rewrite`).
18191809
18201810
See https://cloud.google.com/storage/docs/storage-classes
18211811

packages/google-cloud-storage/tests/system.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,39 @@ def test_rewrite_rotate_with_user_project(self):
963963
retry_429(created.delete)(force=True)
964964

965965

966+
class TestStorageUpdateStorageClass(TestStorageFiles):
967+
968+
def test_update_storage_class_small_file(self):
969+
blob = self.bucket.blob("SmallFile")
970+
971+
file_data = self.FILES["simple"]
972+
blob.upload_from_filename(file_data["path"])
973+
self.case_blobs_to_delete.append(blob)
974+
975+
blob.update_storage_class("NEARLINE")
976+
blob.reload()
977+
self.assertEqual(blob.storage_class, "NEARLINE")
978+
979+
blob.update_storage_class("COLDLINE")
980+
blob.reload()
981+
self.assertEqual(blob.storage_class, "COLDLINE")
982+
983+
def test_update_storage_class_large_file(self):
984+
blob = self.bucket.blob("BigFile")
985+
986+
file_data = self.FILES["big"]
987+
blob.upload_from_filename(file_data["path"])
988+
self.case_blobs_to_delete.append(blob)
989+
990+
blob.update_storage_class("NEARLINE")
991+
blob.reload()
992+
self.assertEqual(blob.storage_class, "NEARLINE")
993+
994+
blob.update_storage_class("COLDLINE")
995+
blob.reload()
996+
self.assertEqual(blob.storage_class, "COLDLINE")
997+
998+
966999
class TestStorageNotificationCRUD(unittest.TestCase):
9671000

9681001
topic = None

packages/google-cloud-storage/tests/unit/test_blob.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2499,10 +2499,43 @@ def test_update_storage_class_invalid(self):
24992499
with self.assertRaises(ValueError):
25002500
blob.update_storage_class(u"BOGUS")
25012501

2502+
def test_update_storage_class_large_file(self):
2503+
BLOB_NAME = "blob-name"
2504+
STORAGE_CLASS = u"NEARLINE"
2505+
TOKEN = "TOKEN"
2506+
INCOMPLETE_RESPONSE = {
2507+
"totalBytesRewritten": 42,
2508+
"objectSize": 84,
2509+
"done": False,
2510+
"rewriteToken": TOKEN,
2511+
"resource": {"storageClass": STORAGE_CLASS}
2512+
}
2513+
COMPLETE_RESPONSE = {
2514+
"totalBytesRewritten": 84,
2515+
"objectSize": 84,
2516+
"done": True,
2517+
"resource": {"storageClass": STORAGE_CLASS}
2518+
}
2519+
response_1 = ({"status": http_client.OK}, INCOMPLETE_RESPONSE)
2520+
response_2 = ({"status": http_client.OK}, COMPLETE_RESPONSE)
2521+
connection = _Connection(response_1, response_2)
2522+
client = _Client(connection)
2523+
bucket = _Bucket(client=client)
2524+
blob = self._make_one(BLOB_NAME, bucket=bucket)
2525+
2526+
blob.update_storage_class("NEARLINE")
2527+
2528+
self.assertEqual(blob.storage_class, "NEARLINE")
2529+
25022530
def test_update_storage_class_wo_encryption_key(self):
25032531
BLOB_NAME = "blob-name"
25042532
STORAGE_CLASS = u"NEARLINE"
2505-
RESPONSE = {"resource": {"storageClass": STORAGE_CLASS}}
2533+
RESPONSE = {
2534+
"totalBytesRewritten": 42,
2535+
"objectSize": 42,
2536+
"done": True,
2537+
"resource": {"storageClass": STORAGE_CLASS}
2538+
}
25062539
response = ({"status": http_client.OK}, RESPONSE)
25072540
connection = _Connection(response)
25082541
client = _Client(connection)
@@ -2542,7 +2575,12 @@ def test_update_storage_class_w_encryption_key_w_user_project(self):
25422575
BLOB_KEY_HASH_B64 = base64.b64encode(BLOB_KEY_HASH).rstrip().decode("ascii")
25432576
STORAGE_CLASS = u"NEARLINE"
25442577
USER_PROJECT = "user-project-123"
2545-
RESPONSE = {"resource": {"storageClass": STORAGE_CLASS}}
2578+
RESPONSE = {
2579+
"totalBytesRewritten": 42,
2580+
"objectSize": 42,
2581+
"done": True,
2582+
"resource": {"storageClass": STORAGE_CLASS}
2583+
}
25462584
response = ({"status": http_client.OK}, RESPONSE)
25472585
connection = _Connection(response)
25482586
client = _Client(connection)

0 commit comments

Comments
 (0)