sagemaker-hyperpod 3.0.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.
- sagemaker/__init__.py +0 -0
- sagemaker/hyperpod/__init__.py +2 -0
- sagemaker/hyperpod/cli/__init__.py +0 -0
- sagemaker/hyperpod/cli/clients/__init__.py +12 -0
- sagemaker/hyperpod/cli/clients/kubernetes_client.py +365 -0
- sagemaker/hyperpod/cli/commands/__init__.py +0 -0
- sagemaker/hyperpod/cli/commands/cluster.py +627 -0
- sagemaker/hyperpod/cli/commands/inference.py +659 -0
- sagemaker/hyperpod/cli/commands/training.py +347 -0
- sagemaker/hyperpod/cli/constants/__init__.py +0 -0
- sagemaker/hyperpod/cli/constants/command_constants.py +105 -0
- sagemaker/hyperpod/cli/constants/exception_constants.py +13 -0
- sagemaker/hyperpod/cli/constants/hyperpod_instance_types.py +100 -0
- sagemaker/hyperpod/cli/constants/kueue_constants.py +17 -0
- sagemaker/hyperpod/cli/constants/pytorch_constants.py +15 -0
- sagemaker/hyperpod/cli/hyp_cli.py +132 -0
- sagemaker/hyperpod/cli/inference_utils.py +121 -0
- sagemaker/hyperpod/cli/service/__init__.py +12 -0
- sagemaker/hyperpod/cli/service/cancel_training_job.py +70 -0
- sagemaker/hyperpod/cli/service/discover_namespaces.py +108 -0
- sagemaker/hyperpod/cli/service/exec_command.py +75 -0
- sagemaker/hyperpod/cli/service/get_logs.py +136 -0
- sagemaker/hyperpod/cli/service/get_namespaces.py +59 -0
- sagemaker/hyperpod/cli/service/get_training_job.py +110 -0
- sagemaker/hyperpod/cli/service/list_pods.py +138 -0
- sagemaker/hyperpod/cli/service/list_training_jobs.py +178 -0
- sagemaker/hyperpod/cli/service/self_subject_access_review.py +47 -0
- sagemaker/hyperpod/cli/telemetry/__init__.py +12 -0
- sagemaker/hyperpod/cli/telemetry/user_agent.py +36 -0
- sagemaker/hyperpod/cli/templates/__init__.py +12 -0
- sagemaker/hyperpod/cli/templates/k8s_pytorch_job_template.py +74 -0
- sagemaker/hyperpod/cli/training_utils.py +200 -0
- sagemaker/hyperpod/cli/utils.py +183 -0
- sagemaker/hyperpod/cli/validators/__init__.py +12 -0
- sagemaker/hyperpod/cli/validators/cluster_validator.py +52 -0
- sagemaker/hyperpod/cli/validators/job_validator.py +346 -0
- sagemaker/hyperpod/cli/validators/validator.py +75 -0
- sagemaker/hyperpod/common/__init__.py +0 -0
- sagemaker/hyperpod/common/config/__init__.py +1 -0
- sagemaker/hyperpod/common/config/metadata.py +18 -0
- sagemaker/hyperpod/common/utils.py +299 -0
- sagemaker/hyperpod/inference/__init__.py +11 -0
- sagemaker/hyperpod/inference/config/__init__.py +0 -0
- sagemaker/hyperpod/inference/config/constants.py +18 -0
- sagemaker/hyperpod/inference/config/hp_endpoint_config.py +767 -0
- sagemaker/hyperpod/inference/config/hp_jumpstart_endpoint_config.py +572 -0
- sagemaker/hyperpod/inference/hp_endpoint.py +203 -0
- sagemaker/hyperpod/inference/hp_endpoint_base.py +223 -0
- sagemaker/hyperpod/inference/hp_jumpstart_endpoint.py +232 -0
- sagemaker/hyperpod/observability/MonitoringConfig.py +10 -0
- sagemaker/hyperpod/observability/__init__.py +0 -0
- sagemaker/hyperpod/observability/constants.py +2 -0
- sagemaker/hyperpod/observability/utils.py +64 -0
- sagemaker/hyperpod/training/__init__.py +9 -0
- sagemaker/hyperpod/training/config/__init__.py +1 -0
- sagemaker/hyperpod/training/config/hyperpod_pytorch_job_config.py +2977 -0
- sagemaker/hyperpod/training/config/hyperpod_pytorch_job_status.py +3083 -0
- sagemaker/hyperpod/training/hyperpod_pytorch_job.py +253 -0
- sagemaker_hyperpod-3.0.0.dist-info/METADATA +534 -0
- sagemaker_hyperpod-3.0.0.dist-info/RECORD +66 -0
- sagemaker_hyperpod-3.0.0.dist-info/WHEEL +5 -0
- sagemaker_hyperpod-3.0.0.dist-info/entry_points.txt +2 -0
- sagemaker_hyperpod-3.0.0.dist-info/licenses/LICENSE +175 -0
- sagemaker_hyperpod-3.0.0.dist-info/licenses/NOTICE +1 -0
- sagemaker_hyperpod-3.0.0.dist-info/top_level.txt +1 -0
- sagemaker_hyperpod-3.0.0.dist-info/zip-safe +1 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
|
5
|
+
# the License is located at
|
|
6
|
+
#
|
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
|
8
|
+
#
|
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
# language governing permissions and limitations under the License.
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import yaml
|
|
16
|
+
from yaml.loader import SafeLoader
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
from sagemaker.hyperpod.cli.clients.kubernetes_client import KubernetesClient
|
|
20
|
+
from sagemaker.hyperpod.cli.constants.command_constants import (
|
|
21
|
+
KUEUE_WORKLOAD_PRIORITY_CLASS_LABEL_KEY,
|
|
22
|
+
SAGEMAKER_TRAINING_LAUNCHER_DIR,
|
|
23
|
+
RestartPolicy,
|
|
24
|
+
KUEUE_QUEUE_NAME_LABEL_KEY,
|
|
25
|
+
HYPERPOD_AUTO_RESUME_ANNOTATION_KEY,
|
|
26
|
+
HYPERPOD_MAX_RETRY_ANNOTATION_KEY,
|
|
27
|
+
INSTANCE_TYPE_LABEL,
|
|
28
|
+
SchedulerType
|
|
29
|
+
)
|
|
30
|
+
from sagemaker.hyperpod.cli.constants.hyperpod_instance_types import (
|
|
31
|
+
HyperpodInstanceType,
|
|
32
|
+
)
|
|
33
|
+
from sagemaker.hyperpod.cli.utils import setup_logger
|
|
34
|
+
from sagemaker.hyperpod.cli.validators.validator import (
|
|
35
|
+
Validator,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
logger = setup_logger(__name__)
|
|
39
|
+
|
|
40
|
+
RECIPES_DIR = os.path.join(SAGEMAKER_TRAINING_LAUNCHER_DIR, "recipes_collection/recipes")
|
|
41
|
+
|
|
42
|
+
class JobValidator(Validator):
|
|
43
|
+
def __init__(self):
|
|
44
|
+
super().__init__()
|
|
45
|
+
|
|
46
|
+
def validate_start_job_args(
|
|
47
|
+
self,
|
|
48
|
+
config_file: Optional[str],
|
|
49
|
+
job_name: Optional[str],
|
|
50
|
+
node_count: Optional[int],
|
|
51
|
+
instance_type: Optional[str],
|
|
52
|
+
image: Optional[str],
|
|
53
|
+
job_kind: Optional[str],
|
|
54
|
+
command: Optional[str],
|
|
55
|
+
label_selector: Optional[str],
|
|
56
|
+
scheduler_type: Optional[str],
|
|
57
|
+
queue_name: Optional[str],
|
|
58
|
+
priority: Optional[str],
|
|
59
|
+
auto_resume: bool,
|
|
60
|
+
restart_policy: Optional[RestartPolicy],
|
|
61
|
+
max_retry: Optional[int],
|
|
62
|
+
namespace: Optional[str],
|
|
63
|
+
entry_script: Optional[str],
|
|
64
|
+
recipe: Optional[str] = None,
|
|
65
|
+
):
|
|
66
|
+
if job_kind is not None and job_kind != "kubeflow/PyTorchJob":
|
|
67
|
+
logger.error("The only supported 'job-kind' is 'kubeflow/PyTorchJob'.")
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
if command is not None and command != "torchrun":
|
|
71
|
+
logger.error("The only supported 'command' is 'torchrun'.")
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
if scheduler_type is not None and scheduler_type not in SchedulerType.get_values():
|
|
75
|
+
logger.error(f"The only supported 'scheduler_type' are {SchedulerType.get_values()}.")
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
if config_file is not None and job_name is not None:
|
|
79
|
+
logger.error(
|
|
80
|
+
"Please provide only 'config-file' to submit job using custom script or 'job-name' to submit job via CLI arguments"
|
|
81
|
+
)
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
if config_file is not None and recipe is not None:
|
|
85
|
+
logger.error(
|
|
86
|
+
"Please provide only 'config-file' to submit job using custom script or 'recipe' for recipe-based jobs"
|
|
87
|
+
)
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
if config_file is None and job_name is None and recipe is None:
|
|
91
|
+
logger.error(
|
|
92
|
+
"Please provide either 'recipe' for recipe-based jobs or 'config-file' to submit job using config file or 'job-name' to submit job via CLI arguments"
|
|
93
|
+
)
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
if job_name is not None:
|
|
97
|
+
if entry_script is None:
|
|
98
|
+
logger.error("Please provide 'entry-script' for the training job")
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
if node_count is None:
|
|
102
|
+
logger.error(
|
|
103
|
+
"Please provide 'node-count' to specify number of nodes used for training job"
|
|
104
|
+
)
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
if image is None:
|
|
108
|
+
logger.error(
|
|
109
|
+
"Please provide 'image' to specify the training image for training job"
|
|
110
|
+
)
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
if label_selector is not None:
|
|
114
|
+
if not _validate_json_str(label_selector):
|
|
115
|
+
logger.error("Please provide valid 'label-selector' JSON string")
|
|
116
|
+
return False
|
|
117
|
+
if not is_dict_str_list_str(json.loads(label_selector)):
|
|
118
|
+
logger.error(
|
|
119
|
+
"Please ensure 'label-selector' keys are string type and values are string or list of string type"
|
|
120
|
+
)
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
if not validate_scheduler_related_fields(scheduler_type, namespace, priority):
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
return validate_hyperpod_related_fields(
|
|
127
|
+
instance_type,
|
|
128
|
+
queue_name,
|
|
129
|
+
priority,
|
|
130
|
+
auto_resume,
|
|
131
|
+
restart_policy,
|
|
132
|
+
max_retry,
|
|
133
|
+
namespace
|
|
134
|
+
)
|
|
135
|
+
if recipe is not None:
|
|
136
|
+
return validate_recipe_file(recipe)
|
|
137
|
+
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def verify_and_load_yaml(file_path: str):
|
|
142
|
+
if not os.path.exists(file_path):
|
|
143
|
+
logger.error(f"Configuration file {file_path} does not exist.")
|
|
144
|
+
return None
|
|
145
|
+
try:
|
|
146
|
+
with open(file_path, "r") as file:
|
|
147
|
+
# Attempt to load the YAML file
|
|
148
|
+
data = yaml.load(file, Loader=SafeLoader)
|
|
149
|
+
return data
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.error(
|
|
152
|
+
f"The config file {file_path} is not a valid YAML file. Error: {e}"
|
|
153
|
+
)
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def validate_yaml_content(data):
|
|
158
|
+
cluster_fields = data.get("cluster")
|
|
159
|
+
if cluster_fields is None:
|
|
160
|
+
logger.error("Please ensure 'cluster' field provided in the config file")
|
|
161
|
+
return False
|
|
162
|
+
cluster_type = cluster_fields.get("cluster_type")
|
|
163
|
+
if cluster_type is None or cluster_type != "k8s":
|
|
164
|
+
logger.error("Only support 'k8s' cluster type currently.")
|
|
165
|
+
return False
|
|
166
|
+
cluster_config_fields = cluster_fields.get("cluster_config")
|
|
167
|
+
if cluster_config_fields is None:
|
|
168
|
+
logger.error(
|
|
169
|
+
"Please ensure 'cluster' contains 'cluster_config' field in the config file"
|
|
170
|
+
)
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
custom_labels = cluster_config_fields.get("custom_labels")
|
|
174
|
+
annotations = cluster_config_fields.get("annotations")
|
|
175
|
+
namespace = cluster_config_fields.get("namespace")
|
|
176
|
+
scheduler_type = cluster_config_fields.get("scheduler_type", SchedulerType.get_default().value)
|
|
177
|
+
|
|
178
|
+
if scheduler_type not in SchedulerType.get_values():
|
|
179
|
+
logger.error(
|
|
180
|
+
f"Unsupported scheduler type '{scheduler_type}', only {SchedulerType.get_values()} are allowed."
|
|
181
|
+
)
|
|
182
|
+
return False
|
|
183
|
+
instance_type = cluster_fields.get("instance_type", None)
|
|
184
|
+
queue_name = None
|
|
185
|
+
if custom_labels is not None:
|
|
186
|
+
queue_name = custom_labels.get(KUEUE_QUEUE_NAME_LABEL_KEY, None)
|
|
187
|
+
|
|
188
|
+
label_selector = cluster_config_fields.setdefault("label_selector",{})
|
|
189
|
+
required_labels = label_selector.get("required", {})
|
|
190
|
+
preferred_labels = label_selector.get("preferred", {})
|
|
191
|
+
|
|
192
|
+
if (
|
|
193
|
+
not required_labels.get(INSTANCE_TYPE_LABEL) and
|
|
194
|
+
not preferred_labels.get(INSTANCE_TYPE_LABEL)
|
|
195
|
+
):
|
|
196
|
+
if "required" not in label_selector:
|
|
197
|
+
label_selector["required"] = {}
|
|
198
|
+
label_selector["required"][INSTANCE_TYPE_LABEL] = (
|
|
199
|
+
[str(instance_type)]
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
auto_resume = False
|
|
203
|
+
max_retry = None
|
|
204
|
+
if annotations is not None:
|
|
205
|
+
auto_resume = annotations.get(HYPERPOD_AUTO_RESUME_ANNOTATION_KEY, False)
|
|
206
|
+
max_retry = annotations.get(HYPERPOD_MAX_RETRY_ANNOTATION_KEY, None)
|
|
207
|
+
if auto_resume and (annotations.get(HYPERPOD_MAX_RETRY_ANNOTATION_KEY) is None):
|
|
208
|
+
logger.error(
|
|
209
|
+
f"Please provide both '{HYPERPOD_AUTO_RESUME_ANNOTATION_KEY}' "
|
|
210
|
+
f"and '{HYPERPOD_MAX_RETRY_ANNOTATION_KEY}' "
|
|
211
|
+
f"annotations to use Auto Resume feature"
|
|
212
|
+
)
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
priority = cluster_config_fields.get("priority_class_name", None)
|
|
216
|
+
restart_policy = cluster_config_fields.get("restartPolicy", None)
|
|
217
|
+
workload_priority = None
|
|
218
|
+
|
|
219
|
+
if custom_labels is not None:
|
|
220
|
+
workload_priority = custom_labels.get(KUEUE_WORKLOAD_PRIORITY_CLASS_LABEL_KEY, None)
|
|
221
|
+
|
|
222
|
+
if not validate_scheduler_related_fields(scheduler_type, namespace, workload_priority):
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
if not validate_hyperpod_related_fields(
|
|
226
|
+
instance_type,
|
|
227
|
+
queue_name,
|
|
228
|
+
priority,
|
|
229
|
+
auto_resume,
|
|
230
|
+
restart_policy,
|
|
231
|
+
max_retry,
|
|
232
|
+
namespace,
|
|
233
|
+
):
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
return True
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def validate_hyperpod_related_fields(
|
|
240
|
+
instance_type: Optional[str],
|
|
241
|
+
queue_name: Optional[str],
|
|
242
|
+
priority: Optional[str],
|
|
243
|
+
auto_resume: bool,
|
|
244
|
+
restart_policy: Optional[RestartPolicy],
|
|
245
|
+
max_retry: Optional[int],
|
|
246
|
+
namespace: Optional[str]
|
|
247
|
+
):
|
|
248
|
+
logger.info(f"instance_type: {instance_type}, queue_name: {queue_name}, priority: {priority}, auto_resume: {auto_resume}, restart_policy: {restart_policy}, max_retry: {max_retry}, namespace: {namespace}")
|
|
249
|
+
if instance_type is None:
|
|
250
|
+
logger.error(
|
|
251
|
+
"Please provide 'instance-type' to specify instance type for training job"
|
|
252
|
+
)
|
|
253
|
+
return False
|
|
254
|
+
else:
|
|
255
|
+
if instance_type not in [member.value for member in HyperpodInstanceType]:
|
|
256
|
+
logger.error("Please provide SageMaker HyperPod supported 'instance-type'")
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
if max_retry and not auto_resume:
|
|
260
|
+
logger.error("Please enable 'auto_resume' with 'max_retry' option.")
|
|
261
|
+
return False
|
|
262
|
+
|
|
263
|
+
if auto_resume and restart_policy != RestartPolicy.ON_FAILURE.value:
|
|
264
|
+
logger.error(
|
|
265
|
+
"To enable 'auto_resume', please ensure the 'restart-policy' is 'OnFailure'. "
|
|
266
|
+
)
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
return True
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def validate_scheduler_related_fields(
|
|
273
|
+
scheduler_type: SchedulerType,
|
|
274
|
+
namespace: Optional[str],
|
|
275
|
+
priority: Optional[str],
|
|
276
|
+
):
|
|
277
|
+
if scheduler_type == SchedulerType.SAGEMAKER.value:
|
|
278
|
+
k8s_client = KubernetesClient()
|
|
279
|
+
sm_managed_namespace = k8s_client.get_sagemaker_managed_namespace(namespace)
|
|
280
|
+
if namespace and not sm_managed_namespace:
|
|
281
|
+
logger.error(
|
|
282
|
+
f"Scheduler type is '{SchedulerType.SAGEMAKER.value}' however cannot find namespace '{namespace}' managed by SageMaker. Please ensure namespace exists and you have 'get' access to it."
|
|
283
|
+
)
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
if priority is not None :
|
|
287
|
+
priority_classes = [item['metadata']['name'] for item in k8s_client.list_workload_priority_classes()['items']]
|
|
288
|
+
if priority not in priority_classes:
|
|
289
|
+
logger.error(
|
|
290
|
+
f"Workload priority class '{priority}' is not found. Please ensure the priority exists and you have 'get' access to 'WorkloadPriorityClass'. Valid priority values are {priority_classes}."
|
|
291
|
+
)
|
|
292
|
+
return False
|
|
293
|
+
return True
|
|
294
|
+
|
|
295
|
+
def validate_recipe_file(recipe: str):
|
|
296
|
+
full_recipe_path = os.path.join(RECIPES_DIR, f"{recipe}.yaml")
|
|
297
|
+
|
|
298
|
+
if os.path.exists(full_recipe_path) and os.path.isfile(full_recipe_path):
|
|
299
|
+
logger.info(f"Recipe file found: {full_recipe_path}")
|
|
300
|
+
return True
|
|
301
|
+
|
|
302
|
+
logger.error(f"Recipe file '{recipe}.yaml' not found in {RECIPES_DIR}")
|
|
303
|
+
return False
|
|
304
|
+
|
|
305
|
+
def is_dict_str_list_str(data: dict) -> bool:
|
|
306
|
+
"""
|
|
307
|
+
Check if the given dictionary is of type Dict[str, List[str]].
|
|
308
|
+
|
|
309
|
+
Parameters:
|
|
310
|
+
data (dict): The dictionary to check.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
bool: True if the dictionary is of type Dict[str, List[str]], False otherwise.
|
|
314
|
+
"""
|
|
315
|
+
for key, value in data.items():
|
|
316
|
+
if not isinstance(value, list) and not isinstance(value, str):
|
|
317
|
+
return False
|
|
318
|
+
elif isinstance(value, list) and not all(
|
|
319
|
+
isinstance(item, str) for item in value
|
|
320
|
+
):
|
|
321
|
+
return False
|
|
322
|
+
return True
|
|
323
|
+
def _validate_json_str(
|
|
324
|
+
json_str: str,
|
|
325
|
+
):
|
|
326
|
+
"""
|
|
327
|
+
Convert a JSON string to a dictionary.
|
|
328
|
+
|
|
329
|
+
Parameters:
|
|
330
|
+
json_string (str): A string in JSON format.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
dict: A dictionary representation of the JSON string.
|
|
334
|
+
"""
|
|
335
|
+
try:
|
|
336
|
+
# Attempt to convert the JSON string to a dictionary
|
|
337
|
+
json.loads(json_str)
|
|
338
|
+
return True
|
|
339
|
+
except json.JSONDecodeError as e:
|
|
340
|
+
# Handle JSON decoding errors
|
|
341
|
+
logger.error(f"JSON decoding failed: {e}")
|
|
342
|
+
return False
|
|
343
|
+
except Exception as e:
|
|
344
|
+
# Catch any other exceptions
|
|
345
|
+
logger.error(f"An unexpected error occurred: {e}")
|
|
346
|
+
return False
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
|
5
|
+
# the License is located at
|
|
6
|
+
#
|
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
|
8
|
+
#
|
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
# language governing permissions and limitations under the License.
|
|
13
|
+
import boto3
|
|
14
|
+
from botocore.exceptions import (
|
|
15
|
+
ClientError,
|
|
16
|
+
NoCredentialsError,
|
|
17
|
+
PartialCredentialsError,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from sagemaker.hyperpod.cli.utils import setup_logger
|
|
21
|
+
|
|
22
|
+
logger = setup_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Validator:
|
|
26
|
+
def __init__(self):
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
def validate(self):
|
|
30
|
+
"""
|
|
31
|
+
Abstract validate method to be implemented in subclasses.
|
|
32
|
+
"""
|
|
33
|
+
return NotImplementedError()
|
|
34
|
+
|
|
35
|
+
def validate_aws_credential(self, session: boto3.Session) -> bool:
|
|
36
|
+
"""
|
|
37
|
+
Validate AWS credentials to ensure AWS credentials configured
|
|
38
|
+
a valida credential exist in current session
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
bool: True aws credentials are valid, False otherwise.
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
# Check if credentials are available
|
|
45
|
+
credentials = session.get_credentials()
|
|
46
|
+
if not credentials:
|
|
47
|
+
logger.error(
|
|
48
|
+
"No AWS credentials found. Please configure your AWS credentials."
|
|
49
|
+
)
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
# Get an STS client to check the credentials
|
|
53
|
+
sts = session.client("sts")
|
|
54
|
+
|
|
55
|
+
# Call get_caller_identity to validate credentials
|
|
56
|
+
sts.get_caller_identity()
|
|
57
|
+
|
|
58
|
+
logger.debug("AWS credentials are valid.")
|
|
59
|
+
return True
|
|
60
|
+
except (NoCredentialsError, PartialCredentialsError) as e:
|
|
61
|
+
logger.error(f"No AWS credentials or partial AWS credentials provided: {e}")
|
|
62
|
+
return False
|
|
63
|
+
except ClientError as e:
|
|
64
|
+
error_code = e.response["Error"]["Code"]
|
|
65
|
+
if error_code == "ExpiredToken":
|
|
66
|
+
logger.error(
|
|
67
|
+
"AWS credentials have expired. Please refresh your AWS "
|
|
68
|
+
"credentials"
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
logger.error(f"Get credentials AWS client error: {e}")
|
|
72
|
+
return False
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(f"Unexpected error to get AWS credentials: {e}")
|
|
75
|
+
return False
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from sagemaker.hyperpod.common.config.metadata import Metadata
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from typing import Dict, Optional
|
|
2
|
+
from pydantic import Field, BaseModel
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Metadata(BaseModel):
|
|
6
|
+
"""Metadata class"""
|
|
7
|
+
|
|
8
|
+
name: str = Field(
|
|
9
|
+
description="Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container."
|
|
10
|
+
)
|
|
11
|
+
namespace: Optional[str] = Field(
|
|
12
|
+
default=None,
|
|
13
|
+
description="Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container.",
|
|
14
|
+
)
|
|
15
|
+
labels: Optional[Dict[str, str]] = Field(
|
|
16
|
+
default=None,
|
|
17
|
+
description="Labels are key value pairs that are attached to objects, such as Pod. Labels are intended to be used to specify identifying attributes of objects. The system ignores labels that are not in the service's selector. Labels can only be added to objects during creation. More info: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
|
18
|
+
)
|