xpk 0.6.0__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. xpk/api/__init__.py +15 -0
  2. xpk/api/storage_crd.yaml +52 -0
  3. xpk/commands/batch.py +27 -5
  4. xpk/commands/cluster.py +104 -80
  5. xpk/commands/cluster_gcluster.py +94 -10
  6. xpk/commands/common.py +44 -0
  7. xpk/commands/config.py +29 -0
  8. xpk/commands/info.py +8 -10
  9. xpk/commands/inspector.py +5 -11
  10. xpk/commands/job.py +9 -7
  11. xpk/commands/kind.py +34 -4
  12. xpk/commands/kjob_common.py +44 -0
  13. xpk/commands/run.py +128 -0
  14. xpk/commands/shell.py +27 -7
  15. xpk/commands/storage.py +267 -0
  16. xpk/commands/version.py +6 -18
  17. xpk/commands/workload.py +381 -184
  18. xpk/core/blueprint/blueprint_definitions.py +1 -0
  19. xpk/core/blueprint/blueprint_generator.py +132 -76
  20. xpk/core/capacity.py +185 -0
  21. xpk/core/cluster.py +564 -0
  22. xpk/core/cluster_private.py +6 -3
  23. xpk/core/commands.py +18 -14
  24. xpk/core/config.py +179 -0
  25. xpk/core/docker_container.py +225 -0
  26. xpk/core/docker_image.py +210 -0
  27. xpk/core/docker_resources.py +350 -0
  28. xpk/core/filestore.py +251 -0
  29. xpk/core/gcloud_context.py +196 -0
  30. xpk/core/gcluster_manager.py +20 -2
  31. xpk/core/gcsfuse.py +50 -0
  32. xpk/core/kjob.py +257 -18
  33. xpk/core/kueue.py +12 -6
  34. xpk/core/monitoring.py +134 -0
  35. xpk/core/nap.py +32 -20
  36. xpk/core/network.py +377 -0
  37. xpk/core/nodepool.py +581 -0
  38. xpk/core/pathways.py +124 -45
  39. xpk/core/remote_state/__init__.py +15 -0
  40. xpk/core/remote_state/fuse_remote_state.py +99 -0
  41. xpk/core/remote_state/remote_state_client.py +38 -0
  42. xpk/core/resources.py +238 -0
  43. xpk/core/scheduling.py +253 -0
  44. xpk/core/storage.py +581 -0
  45. xpk/core/system_characteristics.py +38 -1
  46. xpk/core/vertex.py +105 -0
  47. xpk/core/workload.py +209 -1
  48. xpk/core/workload_decorators/rdma_decorator.py +25 -5
  49. xpk/core/workload_decorators/storage_decorator.py +52 -0
  50. xpk/core/workload_decorators/tcpxo_decorator.py +70 -37
  51. xpk/main.py +3 -1
  52. xpk/parser/batch.py +10 -151
  53. xpk/parser/cluster.py +49 -8
  54. xpk/parser/common.py +189 -1
  55. xpk/parser/config.py +49 -0
  56. xpk/parser/core.py +27 -1
  57. xpk/parser/info.py +2 -1
  58. xpk/parser/inspector.py +3 -3
  59. xpk/parser/job.py +25 -4
  60. xpk/parser/kind.py +3 -2
  61. xpk/parser/run.py +47 -0
  62. xpk/parser/shell.py +10 -1
  63. xpk/parser/storage.py +316 -0
  64. xpk/parser/validators.py +3 -3
  65. xpk/parser/workload.py +118 -76
  66. xpk/templates/__init__.py +15 -0
  67. xpk/templates/storage.yaml +13 -0
  68. xpk/utils/gcs_utils.py +125 -0
  69. xpk/utils/kubectl.py +57 -0
  70. xpk/utils/objects.py +8 -5
  71. xpk/utils/templates.py +28 -0
  72. xpk/utils/validation.py +80 -0
  73. {xpk-0.6.0.dist-info → xpk-0.7.0.dist-info}/METADATA +165 -14
  74. xpk-0.7.0.dist-info/RECORD +92 -0
  75. {xpk-0.6.0.dist-info → xpk-0.7.0.dist-info}/WHEEL +1 -1
  76. xpk/core/core.py +0 -2824
  77. xpk-0.6.0.dist-info/RECORD +0 -57
  78. {xpk-0.6.0.dist-info → xpk-0.7.0.dist-info}/LICENSE +0 -0
  79. {xpk-0.6.0.dist-info → xpk-0.7.0.dist-info}/entry_points.txt +0 -0
  80. {xpk-0.6.0.dist-info → xpk-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,350 @@
1
+ """
2
+ Copyright 2025 Google LLC
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ https://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ from .capacity import H100_DEVICE_TYPE, H100_MEGA_DEVICE_TYPE, H200_DEVICE_TYPE
18
+ from .cluster import setup_k8s_env
19
+ from .storage import GCS_FUSE_TYPE, GCP_FILESTORE_TYPE, Storage, get_storages_to_mount
20
+ from .system_characteristics import AcceleratorType, SystemCharacteristics
21
+
22
+
23
+ def get_main_container_resources(
24
+ args, system: SystemCharacteristics, resource_type
25
+ ) -> str:
26
+ """Resources for the main container.
27
+ Args:
28
+ args: user provided args.
29
+ system: system characteristics.
30
+ resource_type: TPU / GPU / CPU
31
+
32
+ Returns:
33
+ str:
34
+ Workload resources port as a YAML string
35
+ """
36
+ # Resources requirements for Pathways workload containers are known.
37
+ resources_yaml = """cpu: "24"
38
+ memory: 100G"""
39
+ if args.use_pathways:
40
+ return resources_yaml
41
+
42
+ gpu_resources_yaml = """nvidia.com/gpu: {system.chips_per_vm}"""
43
+ if system.accelerator_type == AcceleratorType['GPU']:
44
+ return gpu_resources_yaml.format(system=system)
45
+
46
+ if system.accelerator_type == AcceleratorType['CPU']:
47
+ # CPUs don't have chips, but have a subresource called vCPUs.
48
+ # system.chips_per_vm is used as a proxy for vCPUs.
49
+ # Some vCPUs get used in hosting system pods of the workloads,
50
+ # hence an offset of 0.95 is introduced.
51
+ offset_vCPUs = int(system.chips_per_vm) * 0.95
52
+ return f'{resource_type}: {offset_vCPUs}'
53
+
54
+ return f'{resource_type}: {system.chips_per_vm}'
55
+
56
+
57
+ def get_env_container(args, system: SystemCharacteristics) -> str:
58
+ """Environment configuration for the main container.
59
+ Args:
60
+ args: user provided args.
61
+ system: system characteristics.
62
+
63
+ Returns:
64
+ str:
65
+ YAML with the env config for the main container, as a YAML string.
66
+ """
67
+ pw_env_yaml = """
68
+ - name: XCLOUD_ENVIRONMENT
69
+ value: GCP
70
+ - name: JAX_PLATFORMS
71
+ value: proxy
72
+ - name: JAX_BACKEND_TARGET
73
+ value: {proxy_address}
74
+ - name: JOBSET_NAME
75
+ valueFrom:
76
+ fieldRef:
77
+ fieldPath: metadata.annotations['jobset.sigs.k8s.io/jobset-name']"""
78
+ if args.use_pathways:
79
+ return pw_env_yaml.format(
80
+ args=args, proxy_address=args.pathways_proxy_address
81
+ )
82
+
83
+ gpu_env_yaml = """
84
+ - name: REPLICATED_JOB_NAME
85
+ valueFrom:
86
+ fieldRef:
87
+ fieldPath: metadata.annotations['jobset.sigs.k8s.io/replicatedjob-name']
88
+ - name: JOBSET_NAME
89
+ valueFrom:
90
+ fieldRef:
91
+ fieldPath: metadata.annotations['jobset.sigs.k8s.io/jobset-name']
92
+ - name: JAX_COORDINATOR_ADDRESS
93
+ value: "$(JOBSET_NAME)-$(REPLICATED_JOB_NAME)-0-0.$(JOBSET_NAME)"
94
+ - name: NNODES
95
+ value: "{args.num_nodes}"
96
+ - name: NODE_RANK
97
+ valueFrom:
98
+ fieldRef:
99
+ fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']
100
+ - name: USE_GPUDIRECT
101
+ value: {gpu_direct_name}
102
+ - name: GPUS_PER_NODE
103
+ value: "{system.chips_per_vm}"
104
+ - name: JAX_COORDINATOR_PORT
105
+ value: "6002"
106
+ - name: COMMAND
107
+ value: "{args.command}"
108
+ {args.env}"""
109
+
110
+ if system.accelerator_type == AcceleratorType['GPU']:
111
+ gpu_direct_name = 'fastrak'
112
+ if args.device_type == H100_DEVICE_TYPE:
113
+ gpu_direct_name = 'tcpx'
114
+ gpu_env_yaml += """
115
+ - name: LD_LIBRARY_PATH
116
+ value: /usr/local/nvidia/lib64
117
+ """
118
+ elif args.device_type == H100_MEGA_DEVICE_TYPE:
119
+ gpu_direct_name = 'tcpxo'
120
+ elif args.device_type == H200_DEVICE_TYPE:
121
+ gpu_direct_name = 'rdma'
122
+ return gpu_env_yaml.format(
123
+ args=args, system=system, gpu_direct_name=gpu_direct_name
124
+ )
125
+
126
+ if system.accelerator_type == AcceleratorType['CPU']:
127
+ return get_cpu_env(args.num_slices, args.env, system)
128
+
129
+ return args.env # pytype: disable=bad-return-type
130
+
131
+
132
+ def get_cpu_env(num_slices, env_vars, system) -> str:
133
+ """Generate environment variables for CPU nodepools
134
+ Args:
135
+ num_slices: Number of slices to be used in the workload.
136
+ env_vars: Environment variables, processed from user args.
137
+ system: system characteristics
138
+
139
+ Returns:
140
+ str: yaml containing env variables
141
+ """
142
+ yaml = """
143
+ - name: REPLICATED_JOB_NAME
144
+ valueFrom:
145
+ fieldRef:
146
+ fieldPath: metadata.annotations['jobset.sigs.k8s.io/replicatedjob-name']
147
+ - name: JOB_INDEX
148
+ valueFrom:
149
+ fieldRef:
150
+ fieldPath: metadata.annotations['jobset.sigs.k8s.io/job-index']
151
+ - name: JOB_COMPLETION_INDEX
152
+ valueFrom:
153
+ fieldRef:
154
+ fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']
155
+ - name: PROCESSES_IN_JOB
156
+ value: "{processes_in_job}"
157
+ - name: JAX_PROCESS_COUNT
158
+ value: "{process_count}"
159
+ {env_vars}
160
+ - name: JAX_COORDINATOR_ADDRESS
161
+ value: "$(JOBSET_NAME)-$(REPLICATED_JOB_NAME)-0-0.$(JOBSET_NAME)"
162
+ """
163
+ return yaml.format(
164
+ processes_in_job=system.vms_per_slice,
165
+ process_count=calculate_process_count(num_slices, system.vms_per_slice),
166
+ env_vars=env_vars,
167
+ )
168
+
169
+
170
+ def get_volumes(args, system: SystemCharacteristics) -> str:
171
+ """Get volumes accessible to the containers in the pod.
172
+ Args:
173
+ args: user provided args.
174
+ system: system characteristics.
175
+
176
+ Returns:
177
+ str:
178
+ YAML for the volumes.
179
+ """
180
+ volumes = """- emptyDir:
181
+ medium: Memory
182
+ name: dshm-2
183
+ """
184
+
185
+ if args.ramdisk_directory != '':
186
+ volumes += """
187
+ - name: cache
188
+ csi:
189
+ driver: phase1-checkpoint.csi.storage.gke.io"""
190
+
191
+ if (
192
+ system.accelerator_type == AcceleratorType['TPU']
193
+ and args.deploy_stacktrace_sidecar
194
+ ):
195
+ volumes += """
196
+ - name: tpu-stack-trace
197
+ - name: shared-data
198
+ """
199
+
200
+ storages: list[Storage] = get_storages_to_mount(
201
+ setup_k8s_env(args), args.storage
202
+ )
203
+ for storage in storages:
204
+ if storage.type == GCS_FUSE_TYPE:
205
+ volumes += f"""- name: {storage.pv}
206
+ persistentVolumeClaim:
207
+ claimName: {storage.pvc}
208
+ readOnly: {storage.readonly}
209
+ """
210
+ if storage.type == GCP_FILESTORE_TYPE:
211
+ volumes += f"""- name: {storage.pv}
212
+ persistentVolumeClaim:
213
+ claimName: {storage.pvc}
214
+ readOnly: {storage.readonly}
215
+ """
216
+ return volumes
217
+
218
+
219
+ def get_volume_mounts(args, system: SystemCharacteristics) -> str:
220
+ """Resources for the main container.
221
+ Args:
222
+ args: user provided args.
223
+
224
+ Returns:
225
+ str:
226
+ YAML for the volumes mounted within a Pathways container or GPU container as a YAML string.
227
+ """
228
+ volume_mount_yaml = """- mountPath: /dev/shm
229
+ name: dshm-2
230
+ """
231
+
232
+ if args.ramdisk_directory != '':
233
+ volume_mount_yaml += f"""
234
+ - mountPath: /{args.ramdisk_directory}
235
+ name: cache"""
236
+
237
+ if args.use_pathways:
238
+ volume_mount_yaml = """- mountPath: /tmp
239
+ name: shared-tmp
240
+ """
241
+ elif (
242
+ system.accelerator_type == AcceleratorType['TPU']
243
+ and args.deploy_stacktrace_sidecar
244
+ ):
245
+ volume_mount_yaml += """- name: tpu-stack-trace
246
+ mountPath: /tmp/debugging
247
+ - name: shared-data
248
+ mountPath: /shared-volume
249
+ """
250
+ elif system.accelerator_type == AcceleratorType['GPU']:
251
+ if system.device_type == H100_DEVICE_TYPE:
252
+ volume_mount_yaml = """- name: nvidia-install-dir-host
253
+ mountPath: /usr/local/nvidia/lib64
254
+ - name: tcpx-nccl-plugin-volume
255
+ mountPath: /usr/local/tcpx
256
+ - name: tcpd-socket
257
+ mountPath: /tmp
258
+ - name: shared-memory
259
+ mountPath: /dev/shm
260
+ - name: workload-terminated-volume
261
+ mountPath: /usr/share/workload"""
262
+ elif (
263
+ system.device_type == H100_MEGA_DEVICE_TYPE
264
+ or system.device_type == H200_DEVICE_TYPE
265
+ ):
266
+ volume_mount_yaml = ''
267
+
268
+ storages: list[Storage] = get_storages_to_mount(
269
+ setup_k8s_env(args), args.storage
270
+ )
271
+ for storage in storages:
272
+ if storage.type == GCS_FUSE_TYPE:
273
+ volume_mount_yaml += f"""- name: {storage.pv}
274
+ mountPath: {storage.mount_point}
275
+ readOnly: {storage.readonly}
276
+ """
277
+ if storage.type == GCP_FILESTORE_TYPE:
278
+ volume_mount_yaml += f"""- name: {storage.pv}
279
+ mountPath: {storage.mount_point}
280
+ readOnly: {storage.readonly}
281
+ """
282
+ return volume_mount_yaml
283
+
284
+
285
+ def calculate_process_count(num_slices, vms_per_slice) -> str:
286
+ """Calculates the total number of processes in the workload.
287
+ Args:
288
+ num_slices: Number of slices to be used in the workload.
289
+ vms_per_slice: number of VMs in each slice.
290
+
291
+ Returns:
292
+ str: total number of processes.
293
+ """
294
+ num_processes = int(num_slices) * int(vms_per_slice)
295
+
296
+ return f'{num_processes}'
297
+
298
+
299
+ def add_container_ports(args, system: SystemCharacteristics) -> str:
300
+ """Add slice builder and megascale container ports,
301
+ for non-pathways workloads.
302
+
303
+ Args:
304
+ args: user provided args.
305
+
306
+ Returns:
307
+ str:
308
+ Pathways server port as a YAML string
309
+ """
310
+ port_yaml = """- containerPort: 8471
311
+ - containerPort: 8080"""
312
+ if args.use_pathways:
313
+ return ''
314
+
315
+ gpu_port_yaml = """- containerPort: 6002"""
316
+ if system.accelerator_type == AcceleratorType['GPU']:
317
+ return gpu_port_yaml
318
+ return port_yaml
319
+
320
+
321
+ def add_jax_coordinator_port(system) -> str:
322
+ """Add jax coordinator port only for CPUs
323
+
324
+ Args:
325
+ system: system characteristics.
326
+
327
+ Returns:
328
+ str:
329
+ jax coordinator port as a YAML string
330
+ """
331
+ if system.accelerator_type == AcceleratorType['CPU']:
332
+ return '- containerPort: 1234'
333
+ return ''
334
+
335
+
336
+ def add_image_pull_policy_for_pw_or_gpu(args, system: SystemCharacteristics):
337
+ """Add image pull policy only for Pathways containers.
338
+ Args:
339
+ args: user provided args.
340
+ system: system characteristics
341
+
342
+ Returns:
343
+ str:
344
+ YAML stating that the image will be pulled fro GCR every time.
345
+ """
346
+ yaml = """imagePullPolicy: Always"""
347
+
348
+ if args.use_pathways or system.accelerator_type == AcceleratorType['GPU']:
349
+ return yaml.format(args=args)
350
+ return ''
xpk/core/filestore.py ADDED
@@ -0,0 +1,251 @@
1
+ """
2
+ Copyright 2025 Google LLC
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ https://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ from enum import Enum
18
+
19
+ from google.cloud import filestore_v1
20
+ from google.cloud.exceptions import GoogleCloudError
21
+ from google.cloud.filestore_v1.types import (
22
+ FileShareConfig,
23
+ Instance,
24
+ NetworkConfig,
25
+ )
26
+
27
+ from ..utils import templates
28
+ from ..utils.console import xpk_exit, xpk_print
29
+ from .cluster import zone_to_region
30
+
31
+ FS_PV_PATH = "/../templates/filestore-pv.yaml"
32
+ FS_PVC_PATH = "/../templates/filestore-pvc.yaml"
33
+ FS_SC_PATH = "/../templates/filestore-sc.yaml"
34
+
35
+
36
+ class Availability(Enum):
37
+ ZONAL = "Zonal"
38
+ REGIONAL = "Regional"
39
+
40
+
41
+ TIERS = {
42
+ "BASIC_HDD": Availability.ZONAL,
43
+ "BASIC_SSD": Availability.ZONAL,
44
+ "ZONAL": Availability.ZONAL,
45
+ "REGIONAL": Availability.REGIONAL,
46
+ "ENTERPRISE": Availability.REGIONAL,
47
+ }
48
+
49
+
50
+ def get_storage_class_name(storage_name: str) -> str:
51
+ return f"{storage_name}-sc"
52
+
53
+
54
+ def get_pv_name(storage_name: str) -> str:
55
+ return f"{storage_name}-pv"
56
+
57
+
58
+ def get_pvc_name(storage_name: str) -> str:
59
+ return f"{storage_name}-pvc"
60
+
61
+
62
+ class FilestoreClient:
63
+ """FilestoreClient is a class for interacting with GCP filestore instances."""
64
+
65
+ def __init__(
66
+ self,
67
+ zone: str,
68
+ name: str,
69
+ project: str,
70
+ ) -> None:
71
+ self.zone = zone
72
+ self.region = zone_to_region(zone)
73
+ self.name = name
74
+ self.project = project
75
+ self._client = filestore_v1.CloudFilestoreManagerClient()
76
+ self.instance: Instance | None = None
77
+
78
+ def get_instance(self) -> Instance | None:
79
+ """Get existing Filestore instance"""
80
+ parentZonal = self.get_parent(self.zone)
81
+ parentRegional = self.get_parent(self.region)
82
+ reqZonal = filestore_v1.ListInstancesRequest(parent=parentZonal)
83
+ reqRegional = filestore_v1.ListInstancesRequest(parent=parentRegional)
84
+ try:
85
+ instancesZonal = self._client.list_instances(reqZonal)
86
+ instancesRegional = self._client.list_instances(reqRegional)
87
+ except GoogleCloudError as e:
88
+ xpk_print(f"Exception while trying to list instances {e}")
89
+ xpk_exit(1)
90
+
91
+ fullname_zonal = self.get_instance_fullname(self.zone)
92
+ fullname_regional = self.get_instance_fullname(self.region)
93
+
94
+ for instance in instancesZonal:
95
+ if instance.name == fullname_zonal:
96
+ return instance # pytype: disable=bad-return-type
97
+
98
+ for instance in instancesRegional:
99
+ if instance.name == fullname_regional:
100
+ return instance # pytype: disable=bad-return-type
101
+
102
+ def check_instance_exists(self) -> bool:
103
+ """Check if Filestore instance exists"""
104
+ instance = self.get_instance()
105
+ return instance is not None
106
+
107
+ def load_instance(self) -> None:
108
+ if self.instance is None:
109
+ self.instance = self.get_instance()
110
+
111
+ def get_instance_location(self) -> str:
112
+ """Get Filestore instance's location"""
113
+ self.load_instance()
114
+ return str(self.instance.name.split("/")[3])
115
+
116
+ def create_instance(
117
+ self,
118
+ vol: str,
119
+ size: int,
120
+ tier: str,
121
+ connect_mode=None,
122
+ reserved_ip_range=None,
123
+ network: str = "default",
124
+ description: str = "XPK created filestore instance",
125
+ kms_key_name=None,
126
+ source_backup=None,
127
+ nfs_export_options=None,
128
+ modes=None,
129
+ ) -> None:
130
+ """Create new Filestore instance"""
131
+
132
+ location = (
133
+ self.zone
134
+ if TIERS[tier].value == Availability.ZONAL.value
135
+ else self.region
136
+ )
137
+
138
+ file_shares = [
139
+ FileShareConfig(
140
+ name=vol,
141
+ capacity_gb=size,
142
+ source_backup=source_backup,
143
+ nfs_export_options=nfs_export_options,
144
+ )
145
+ ]
146
+ networks = [
147
+ NetworkConfig(
148
+ network=network,
149
+ modes=modes,
150
+ reserved_ip_range=reserved_ip_range,
151
+ connect_mode=connect_mode,
152
+ )
153
+ ]
154
+ request = filestore_v1.CreateInstanceRequest(
155
+ parent=self.get_parent(location),
156
+ instance_id=self.name,
157
+ instance=Instance(
158
+ description=description,
159
+ tier=tier,
160
+ kms_key_name=kms_key_name,
161
+ file_shares=file_shares,
162
+ networks=networks,
163
+ ),
164
+ )
165
+ # Make the request
166
+ operation = self._client.create_instance(request=request)
167
+ xpk_print("Waiting for filestore creation to complete...")
168
+ self.instance = None
169
+ try:
170
+ self.instance = operation.result()
171
+ except GoogleCloudError as e:
172
+ xpk_print(f"Error while creating Filestore instance: {e}")
173
+ xpk_exit(1)
174
+ xpk_print(
175
+ f"Filestore instance {self.get_instance_fullname(location)} created"
176
+ )
177
+
178
+ def delete_filestore_instance(self):
179
+ # Initialize request
180
+ name = self.get_instance_fullname()
181
+ request = filestore_v1.DeleteInstanceRequest(name=name)
182
+
183
+ # Make the request
184
+ operation = self._client.delete_instance(request)
185
+ xpk_print("Waiting for filestore deletion to complete...")
186
+ try:
187
+ operation.result()
188
+ except GoogleCloudError as e:
189
+ xpk_print(f"Error while deleting Filestore instance: {e}")
190
+ xpk_exit(1)
191
+ xpk_print(f"Filestore instance {name} deleted")
192
+
193
+ def create_sc(self, name: str, network: str) -> dict:
194
+ """Create a yaml representing filestore StorageClass."""
195
+ data = templates.load(FS_SC_PATH)
196
+ data["metadata"]["name"] = get_storage_class_name(name)
197
+ data["parameters"]["tier"] = self.instance.tier.name
198
+ data["parameters"][
199
+ "network"
200
+ ] = f"projects/{self.project}/global/networks/{network}"
201
+ return data
202
+
203
+ def create_pv(self, name: str, vol: str, access_mode: str) -> dict:
204
+ """Create a yaml representing filestore PersistentVolume."""
205
+ data = templates.load(FS_PV_PATH)
206
+ data["metadata"]["name"] = get_pv_name(name)
207
+ data["spec"]["storageClassName"] = get_storage_class_name(name)
208
+ data["spec"]["capacity"]["storage"] = self.instance.file_shares[
209
+ 0
210
+ ].capacity_gb
211
+ data["spec"]["accessModes"] = [access_mode]
212
+ volumeHandle = f"{self.get_instance_fullname()}/volumes/{vol}"
213
+ data["spec"]["csi"]["volumeHandle"] = volumeHandle
214
+ data["spec"]["csi"]["volumeAttributes"]["ip"] = self.instance.networks[
215
+ 0
216
+ ].ip_addresses[0]
217
+ data["spec"]["csi"]["volumeAttributes"]["volume"] = vol
218
+ return data
219
+
220
+ def create_pvc(self, name: str, access_mode: str) -> dict:
221
+ """Create a yaml representing filestore PersistentVolumeClaim."""
222
+ data = templates.load(FS_PVC_PATH)
223
+ data["metadata"]["name"] = get_pvc_name(name)
224
+ data["spec"]["accessModes"] = [access_mode]
225
+ data["spec"]["storageClassName"] = get_storage_class_name(name)
226
+ data["spec"]["volumeName"] = get_pv_name(name)
227
+ data["spec"]["resources"]["requests"]["storage"] = (
228
+ self.instance.file_shares[0].capacity_gb
229
+ )
230
+ return data
231
+
232
+ def manifest(
233
+ self, name: str, vol: str, access_mode: str, network: str
234
+ ) -> list[dict]:
235
+ self.load_instance()
236
+ pv = self.create_pv(name, vol, access_mode)
237
+ pvc = self.create_pvc(name, access_mode)
238
+ sc = self.create_sc(name, network)
239
+ return [pv, pvc, sc]
240
+
241
+ def get_parent(self, location: str | None = None) -> str:
242
+ """Get the Filestore's parent's name"""
243
+ if location is None:
244
+ location = self.get_instance_location()
245
+ return f"projects/{self.project}/locations/{location}"
246
+
247
+ def get_instance_fullname(self, location: str | None = None) -> str:
248
+ """Get the Filestore's full name"""
249
+ if location is None:
250
+ location = self.get_instance_location()
251
+ return f"projects/{self.project}/locations/{location}/instances/{self.name}"