Skip to content

Commit 27c735f

Browse files
committed
Create secondary custom disk Sample
Updated tests approach
1 parent d0f51e4 commit 27c735f

File tree

2 files changed

+201
-35
lines changed

2 files changed

+201
-35
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Copyright 2024 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
# flake8: noqa
15+
16+
from __future__ import annotations
17+
18+
import sys
19+
from typing import Any
20+
21+
from google.api_core.extended_operation import ExtendedOperation
22+
from google.cloud import compute_v1
23+
24+
25+
def wait_for_extended_operation(
26+
operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300
27+
) -> Any:
28+
"""
29+
Waits for the extended (long-running) operation to complete.
30+
If the operation is successful, it will return its result.
31+
If the operation ends with an error, an exception will be raised.
32+
If there were any warnings during the execution of the operation
33+
they will be printed to sys.stderr.
34+
Args:
35+
operation: a long-running operation you want to wait on.
36+
verbose_name: (optional) a more verbose name of the operation,
37+
used only during error and warning reporting.
38+
timeout: how long (in seconds) to wait for operation to finish.
39+
If None, wait indefinitely.
40+
Returns:
41+
Whatever the operation.result() returns.
42+
Raises:
43+
This method will raise the exception received from `operation.exception()`
44+
or RuntimeError if there is no exception set, but there is an `error_code`
45+
set for the `operation`.
46+
In case of an operation taking longer than `timeout` seconds to complete,
47+
a `concurrent.futures.TimeoutError` will be raised.
48+
"""
49+
result = operation.result(timeout=timeout)
50+
51+
if operation.error_code:
52+
print(
53+
f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}",
54+
file=sys.stderr,
55+
flush=True,
56+
)
57+
print(f"Operation ID: {operation.name}", file=sys.stderr, flush=True)
58+
raise operation.exception() or RuntimeError(operation.error_message)
59+
60+
if operation.warnings:
61+
print(f"Warnings during {verbose_name}:\n", file=sys.stderr, flush=True)
62+
for warning in operation.warnings:
63+
print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True)
64+
65+
return result
66+
67+
68+
def create_secondary_custom_disk(
69+
primary_disk_name: str,
70+
primary_disk_project: str,
71+
primary_disk_zone: str,
72+
secondary_disk_name: str,
73+
secondary_disk_project: str,
74+
secondary_disk_zone: str,
75+
disk_size_gb: int,
76+
disk_type: str = "pd-ssd",
77+
78+
) -> compute_v1.Disk:
79+
"""Creates a custom secondary disk whose properties differ from the primary disk.
80+
Args:
81+
primary_disk_name (str): The name of the primary disk.
82+
primary_disk_project (str): The project of the primary disk.
83+
primary_disk_zone (str): The location of the primary disk.
84+
secondary_disk_name (str): The name of the secondary disk.
85+
secondary_disk_project (str): The project of the secondary disk.
86+
secondary_disk_zone (str): The location of the secondary disk.
87+
disk_size_gb (int): The size of the disk in GB. Should be the same as the primary disk.
88+
disk_type (str): The type of the disk. Must be one of pd-ssd or pd-balanced.
89+
"""
90+
disk_client = compute_v1.DisksClient()
91+
disk = compute_v1.Disk()
92+
disk.name = secondary_disk_name
93+
disk.size_gb = disk_size_gb
94+
disk.type = f"zones/{primary_disk_zone}/diskTypes/{disk_type}"
95+
disk.async_primary_disk = compute_v1.DiskAsyncReplication(
96+
disk=f"projects/{primary_disk_project}/zones/{primary_disk_zone}/disks/{primary_disk_name}"
97+
)
98+
99+
# Specify additional guest OS features for the secondary disk
100+
# Set to one or more of the following values: - VIRTIO_SCSI_MULTIQUEUE - WINDOWS - MULTI_IP_SUBNET
101+
# - UEFI_COMPATIBLE - GVNIC - SEV_CAPABLE - SUSPEND_RESUME_COMPATIBLE
102+
# - SEV_LIVE_MIGRATABLE_V2 - SEV_SNP_CAPABLE - TDX_CAPABLE - IDPF
103+
disk.guest_os_features = [compute_v1.GuestOsFeature(type="MULTI_IP_SUBNET")]
104+
105+
# Assign additional labels to the secondary disk
106+
disk.labels = {
107+
"source-disk": primary_disk_name,
108+
"secondary-disk-for-replication": "true",
109+
}
110+
111+
operation = disk_client.insert(
112+
project=secondary_disk_project, zone=secondary_disk_zone, disk_resource=disk
113+
)
114+
wait_for_extended_operation(operation, "create_secondary_disk")
115+
116+
secondary_disk = disk_client.get(
117+
project=secondary_disk_project,
118+
zone=secondary_disk_zone,
119+
disk=secondary_disk_name,
120+
)
121+
return secondary_disk
122+
123+
124+
if __name__ == "__main__":
125+
import os
126+
127+
PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT")
128+
a = create_secondary_custom_disk(
129+
primary_disk_name="disk-1",
130+
primary_disk_project=PROJECT_ID,
131+
primary_disk_zone="europe-west2-c",
132+
secondary_disk_name="secondary-disk-name6",
133+
secondary_disk_project=PROJECT_ID,
134+
secondary_disk_zone="europe-west1-c",
135+
disk_size_gb=100,
136+
disk_type="pd-ssd",
137+
)
138+
print(a.labels)
139+
print(a.labels["secondary-disk-for-replication"])
140+
print(a.labels["source-disk"])

compute/client_library/snippets/tests/test_disks.py

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from ..disks.create_hyperdisk_from_pool import create_hyperdisk_from_pool
2929
from ..disks.create_hyperdisk_storage_pool import create_hyperdisk_storage_pool
3030
from ..disks.create_kms_encrypted_disk import create_kms_encrypted_disk
31+
from ..disks.create_secondary_custom import create_secondary_custom_disk
3132
from ..disks.create_secondary_disk import create_secondary_disk
3233
from ..disks.create_secondary_region_disk import create_secondary_region_disk
3334
from ..disks.delete import delete_disk
@@ -45,10 +46,12 @@
4546

4647
PROJECT = google.auth.default()[1]
4748
ZONE = "europe-west2-c"
49+
ZONE_SECONDARY = "europe-west1-c"
4850
REGION = "europe-west2"
49-
SECOND_REGION = "europe-central2"
51+
REGION_SECONDARY = "europe-central2"
5052
KMS_KEYRING_NAME = "compute-test-keyring"
5153
KMS_KEY_NAME = "compute-test-key"
54+
DISK_SIZE = 11
5255

5356

5457
@pytest.fixture()
@@ -96,6 +99,23 @@ def test_disk():
9699
delete_disk(PROJECT, ZONE, test_disk_name)
97100

98101

102+
@pytest.fixture
103+
def test_empty_pd_balanced_disk():
104+
"""
105+
Creates and deletes a pd_balanced disk in secondary zone.
106+
"""
107+
disk_name = "test-pd-balanced-disk" + uuid.uuid4().hex[:4]
108+
disk = create_empty_disk(
109+
PROJECT,
110+
ZONE_SECONDARY,
111+
disk_name,
112+
f"zones/{ZONE_SECONDARY}/diskTypes/pd-balanced",
113+
disk_size_gb=DISK_SIZE,
114+
)
115+
yield disk
116+
delete_disk(PROJECT, ZONE_SECONDARY, disk_name)
117+
118+
99119
@pytest.fixture
100120
def test_snapshot(test_disk):
101121
"""
@@ -110,22 +130,22 @@ def test_snapshot(test_disk):
110130

111131

112132
@pytest.fixture()
113-
def autodelete_disk_name():
114-
disk_name = "test-disk-" + uuid.uuid4().hex[:10]
133+
def autodelete_regional_disk_name():
134+
disk_name = "secondary-region-disk" + uuid.uuid4().hex[:4]
115135
yield disk_name
116136
try:
117-
delete_disk(PROJECT, ZONE, disk_name)
137+
delete_regional_disk(PROJECT, REGION_SECONDARY, disk_name)
118138
except NotFound:
119139
# The disk was already deleted
120140
pass
121141

122142

123143
@pytest.fixture()
124-
def autodelete_regional_disk_name():
125-
disk_name = "secondary-region-disk" + uuid.uuid4().hex[:4]
144+
def autodelete_disk_name():
145+
disk_name = "test-disk-" + uuid.uuid4().hex[:10]
126146
yield disk_name
127147
try:
128-
delete_regional_disk(PROJECT, SECOND_REGION, disk_name)
148+
delete_disk(PROJECT, ZONE, disk_name)
129149
except NotFound:
130150
# The disk was already deleted
131151
pass
@@ -165,7 +185,7 @@ def autodelete_regional_blank_disk():
165185
disk_type = f"regions/{REGION}/diskTypes/pd-balanced"
166186

167187
disk = create_regional_disk(
168-
PROJECT, REGION, replica_zones, disk_name, disk_type, 11
188+
PROJECT, REGION, replica_zones, disk_name, disk_type, DISK_SIZE
169189
)
170190

171191
yield disk
@@ -379,31 +399,6 @@ def test_create_hyperdisk(autodelete_disk_name):
379399
assert "hyperdisk" in disk.type_.lower()
380400

381401

382-
def test_create_secondary(autodelete_disk_name):
383-
disk_size = 12
384-
primary_disk = create_empty_disk(
385-
PROJECT,
386-
ZONE,
387-
autodelete_disk_name,
388-
f"zones/{ZONE}/diskTypes/pd-balanced",
389-
disk_size_gb=disk_size,
390-
)
391-
disk_name = "secondary-region-disk" + uuid.uuid4().hex[:4]
392-
disk = create_secondary_disk(
393-
autodelete_disk_name,
394-
PROJECT,
395-
ZONE,
396-
disk_name,
397-
PROJECT,
398-
"europe-west3-c",
399-
disk_size,
400-
)
401-
try:
402-
assert disk.async_primary_disk.disk == primary_disk.self_link
403-
finally:
404-
delete_disk(PROJECT, "europe-west3-c", disk_name)
405-
406-
407402
def test_create_secondary_region(
408403
autodelete_regional_blank_disk, autodelete_regional_disk_name
409404
):
@@ -413,7 +408,38 @@ def test_create_secondary_region(
413408
REGION,
414409
autodelete_regional_disk_name,
415410
PROJECT,
416-
SECOND_REGION,
417-
11,
411+
REGION_SECONDARY,
412+
DISK_SIZE,
418413
)
419414
assert disk.async_primary_disk.disk == autodelete_regional_blank_disk.self_link
415+
416+
417+
def test_create_secondary(test_empty_pd_balanced_disk, autodelete_disk_name):
418+
disk = create_secondary_disk(
419+
primary_disk_name=test_empty_pd_balanced_disk.name,
420+
primary_disk_project=PROJECT,
421+
primary_disk_zone=ZONE_SECONDARY,
422+
secondary_disk_name=autodelete_disk_name,
423+
secondary_disk_project=PROJECT,
424+
secondary_disk_zone=ZONE,
425+
disk_size_gb=DISK_SIZE,
426+
disk_type="pd-ssd",
427+
)
428+
assert disk.async_primary_disk.disk == test_empty_pd_balanced_disk.self_link
429+
430+
431+
def test_create_custom_secondary_disk(
432+
test_empty_pd_balanced_disk, autodelete_disk_name
433+
):
434+
disk = create_secondary_custom_disk(
435+
primary_disk_name=test_empty_pd_balanced_disk.name,
436+
primary_disk_project=PROJECT,
437+
primary_disk_zone=ZONE_SECONDARY,
438+
secondary_disk_name=autodelete_disk_name,
439+
secondary_disk_project=PROJECT,
440+
secondary_disk_zone=ZONE,
441+
disk_size_gb=DISK_SIZE,
442+
disk_type="pd-ssd",
443+
)
444+
assert disk.labels["secondary-disk-for-replication"] == "true"
445+
assert disk.labels["source-disk"] == test_empty_pd_balanced_disk.name

0 commit comments

Comments
 (0)