Skip to content

Commit 0571240

Browse files
feat: Attach regional disk force sample (#13008)
* New attach regional disk force sample
1 parent e789679 commit 0571240

File tree

4 files changed

+212
-0
lines changed

4 files changed

+212
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
15+
# This is an ingredient file. It is not meant to be run directly. Check the samples/snippets
16+
# folder for complete code samples that are ready to be used.
17+
# Disabling flake8 for the ingredients file, as it would fail F821 - undefined name check.
18+
# flake8: noqa
19+
from google.cloud import compute_v1
20+
21+
22+
# <INGREDIENT attach_disk_force>
23+
24+
25+
def attach_disk_force(
26+
project_id: str, vm_name: str, vm_zone: str, disk_name: str, disk_region: str
27+
) -> None:
28+
"""
29+
Force-attaches a regional disk to a compute instance, even if it is
30+
still attached to another instance. Useful when the original instance
31+
cannot be reached or disconnected.
32+
Args:
33+
project_id (str): The ID of the Google Cloud project.
34+
vm_name (str): The name of the compute instance you want to attach a disk to.
35+
vm_zone (str): The zone where the compute instance is located.
36+
disk_name (str): The name of the disk to be attached.
37+
disk_region (str): The region where the disk is located.
38+
Returns:
39+
None
40+
"""
41+
client = compute_v1.InstancesClient()
42+
disk = compute_v1.AttachedDisk(
43+
source=f"projects/{project_id}/regions/{disk_region}/disks/{disk_name}"
44+
)
45+
46+
request = compute_v1.AttachDiskInstanceRequest(
47+
attached_disk_resource=disk,
48+
force_attach=True,
49+
instance=vm_name,
50+
project=project_id,
51+
zone=vm_zone,
52+
)
53+
operation = client.attach_disk(request=request)
54+
wait_for_extended_operation(operation, "force disk attachment")
55+
56+
57+
# </INGREDIENT>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
# <REGION compute_instance_attach_regional_disk_force>
17+
18+
# <IMPORTS/>
19+
20+
# <INGREDIENT wait_for_extended_operation />
21+
22+
# <INGREDIENT attach_disk_force />
23+
24+
# </REGION compute_instance_attach_regional_disk_force>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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+
17+
# This file is automatically generated. Please do not modify it directly.
18+
# Find the relevant recipe file in the samples/recipes or samples/ingredients
19+
# directory and apply your changes there.
20+
21+
22+
# [START compute_instance_attach_regional_disk_force]
23+
24+
from __future__ import annotations
25+
26+
import sys
27+
from typing import Any
28+
29+
from google.api_core.extended_operation import ExtendedOperation
30+
from google.cloud import compute_v1
31+
32+
33+
def wait_for_extended_operation(
34+
operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300
35+
) -> Any:
36+
"""
37+
Waits for the extended (long-running) operation to complete.
38+
39+
If the operation is successful, it will return its result.
40+
If the operation ends with an error, an exception will be raised.
41+
If there were any warnings during the execution of the operation
42+
they will be printed to sys.stderr.
43+
44+
Args:
45+
operation: a long-running operation you want to wait on.
46+
verbose_name: (optional) a more verbose name of the operation,
47+
used only during error and warning reporting.
48+
timeout: how long (in seconds) to wait for operation to finish.
49+
If None, wait indefinitely.
50+
51+
Returns:
52+
Whatever the operation.result() returns.
53+
54+
Raises:
55+
This method will raise the exception received from `operation.exception()`
56+
or RuntimeError if there is no exception set, but there is an `error_code`
57+
set for the `operation`.
58+
59+
In case of an operation taking longer than `timeout` seconds to complete,
60+
a `concurrent.futures.TimeoutError` will be raised.
61+
"""
62+
result = operation.result(timeout=timeout)
63+
64+
if operation.error_code:
65+
print(
66+
f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}",
67+
file=sys.stderr,
68+
flush=True,
69+
)
70+
print(f"Operation ID: {operation.name}", file=sys.stderr, flush=True)
71+
raise operation.exception() or RuntimeError(operation.error_message)
72+
73+
if operation.warnings:
74+
print(f"Warnings during {verbose_name}:\n", file=sys.stderr, flush=True)
75+
for warning in operation.warnings:
76+
print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True)
77+
78+
return result
79+
80+
81+
def attach_disk_force(
82+
project_id: str, vm_name: str, vm_zone: str, disk_name: str, disk_region: str
83+
) -> None:
84+
"""
85+
Force-attaches a regional disk to a compute instance, even if it is
86+
still attached to another instance. Useful when the original instance
87+
cannot be reached or disconnected.
88+
Args:
89+
project_id (str): The ID of the Google Cloud project.
90+
vm_name (str): The name of the compute instance you want to attach a disk to.
91+
vm_zone (str): The zone where the compute instance is located.
92+
disk_name (str): The name of the disk to be attached.
93+
disk_region (str): The region where the disk is located.
94+
Returns:
95+
None
96+
"""
97+
client = compute_v1.InstancesClient()
98+
disk = compute_v1.AttachedDisk(
99+
source=f"projects/{project_id}/regions/{disk_region}/disks/{disk_name}"
100+
)
101+
102+
request = compute_v1.AttachDiskInstanceRequest(
103+
attached_disk_resource=disk,
104+
force_attach=True,
105+
instance=vm_name,
106+
project=project_id,
107+
zone=vm_zone,
108+
)
109+
operation = client.attach_disk(request=request)
110+
wait_for_extended_operation(operation, "force disk attachment")
111+
112+
113+
# [END compute_instance_attach_regional_disk_force]

compute/client_library/snippets/tests/test_disks.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import pytest
2222

2323
from ..disks.attach_disk import attach_disk
24+
from ..disks.attach_regional_disk_force import attach_disk_force
2425
from ..disks.attach_regional_disk_to_vm import attach_regional_disk
2526
from ..disks.clone_encrypted_disk_managed_key import create_disk_from_kms_encrypted_disk
2627
from ..disks.create_empty_disk import create_empty_disk
@@ -380,6 +381,23 @@ def test_disk_attachment(
380381
assert len(list(instance.disks)) == 3
381382

382383

384+
def test_regional_disk_force_attachment(
385+
autodelete_regional_blank_disk, autodelete_compute_instance
386+
):
387+
attach_disk_force(
388+
project_id=PROJECT,
389+
vm_name=autodelete_compute_instance.name,
390+
vm_zone=ZONE,
391+
disk_name=autodelete_regional_blank_disk.name,
392+
disk_region=REGION,
393+
)
394+
395+
instance = get_instance(PROJECT, ZONE, autodelete_compute_instance.name)
396+
assert any(
397+
[autodelete_regional_blank_disk.name in disk.source for disk in instance.disks]
398+
)
399+
400+
383401
def test_disk_resize(autodelete_blank_disk, autodelete_regional_blank_disk):
384402
resize_disk(PROJECT, autodelete_blank_disk.self_link, 22)
385403
resize_disk(PROJECT, autodelete_regional_blank_disk.self_link, 23)

0 commit comments

Comments
 (0)