Skip to content

Commit 3d58dc9

Browse files
deeppcspapriwal
authored andcommitted
test(instance-preferences): enforce the public contract in integ tests (#2158)
Instance Preferences is publicly released, so the integ tests no longer need the pre-release escape hatches: the XFAIL branches for gated Describe responses, model @required rejections, and InternalServerError before type resolution are removed, along with the rollout notes that explained them. Both tests now assert the full contract unconditionally: create accepted, preferences echoed, top-level instance type absent, winner among the submitted preferences with the expected count. The processing test used a private loader for the bundled service model; the public botocore model now carries the shapes, so it uses a plain boto3 client. Candidate instance types are overridable via INSTANCE_PREFERENCES_TEST_INSTANCE_TYPES / PROCESSING_INSTANCE_PREFERENCES_TEST_INSTANCE_TYPES so the tests can run in accounts where the default types are quota-saturated. Verified against prod us-west-2: processing passes (winner resolved, 3 min); training passes (winner resolved, 8 min).
1 parent 560f0ed commit 3d58dc9

2 files changed

Lines changed: 32 additions & 85 deletions

File tree

‎sagemaker-core/tests/integ/processing/test_instance_preferences_processing.py‎

Lines changed: 16 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,12 @@
2828
2929
- ``PROCESSING_INSTANCE_PREFERENCES_TEST_ROLE_ARN`` - execution role ARN
3030
- ``PROCESSING_INSTANCE_PREFERENCES_TEST_IMAGE_URI`` - processing image
31+
- ``PROCESSING_INSTANCE_PREFERENCES_TEST_INSTANCE_TYPES`` - optional comma-separated
32+
candidate types (default: ml.m5.xlarge, ml.m5.large)
3133
- ``SAGEMAKER_ENDPOINT`` - optional endpoint
32-
override (e.g. a pre-GA stage endpoint).
33-
34-
Rollout note: like the training analog
35-
(``sagemaker-train/tests/integ/train/test_instance_preferences.py``), while
36-
the server-side scheduling/write-back stages are not yet deployed on the
37-
target endpoint an accepted job fails with ``InternalServerError`` before an
38-
instance type is resolved; the test reports that as XFAIL (rollout
39-
incomplete) and enforces the full contract automatically once deployed.
34+
override.
4035
"""
36+
4137
from __future__ import absolute_import
4238

4339
import os
@@ -50,7 +46,13 @@
5046
IMAGE_URI = os.environ.get("PROCESSING_INSTANCE_PREFERENCES_TEST_IMAGE_URI")
5147
ENDPOINT = os.environ.get("SAGEMAKER_ENDPOINT")
5248

53-
PREFERENCE_TYPES = ["ml.m5.xlarge", "ml.m5.large"]
49+
PREFERENCE_TYPES = [
50+
t.strip()
51+
for t in os.environ.get(
52+
"PROCESSING_INSTANCE_PREFERENCES_TEST_INSTANCE_TYPES", "ml.m5.xlarge,ml.m5.large"
53+
).split(",")
54+
if t.strip()
55+
]
5456
WAIT_TIMEOUT_SECONDS = 30 * 60
5557
POLL_SECONDS = 30
5658

@@ -65,27 +67,11 @@
6567

6668

6769
def _sagemaker_client():
68-
"""Build a botocore client on the bundled service model (which carries the
69-
pre-GA InstancePreferences shapes), honoring SAGEMAKER_ENDPOINT."""
70-
import pathlib
71-
72-
import botocore.loaders
73-
import botocore.session as bc_session_mod
74-
from boto3.session import Session as Boto3Session
75-
76-
# .../sagemaker-core/tests/integ/processing/<this file> -> .../sagemaker-core/sample
77-
sample_dir = pathlib.Path(__file__).resolve().parents[3] / "sample"
78-
assert (sample_dir / "sagemaker").exists(), f"bundled model dir not found: {sample_dir}"
79-
sample_dir = str(sample_dir)
80-
bc_session = bc_session_mod.get_session()
81-
loader = botocore.loaders.Loader(
82-
extra_search_paths=[sample_dir], include_default_search_paths=True
83-
)
84-
bc_session.register_component("data_loader", loader)
70+
"""Build a boto3 SageMaker client, honoring SAGEMAKER_ENDPOINT."""
71+
import boto3
72+
8573
region = os.environ.get("AWS_REGION", "us-west-2")
86-
return Boto3Session(botocore_session=bc_session, region_name=region).client(
87-
"sagemaker", endpoint_url=ENDPOINT
88-
)
74+
return boto3.client("sagemaker", region_name=region, endpoint_url=ENDPOINT)
8975

9076

9177
def test_processor_instance_preferences_e2e():
@@ -107,26 +93,7 @@ def test_processor_instance_preferences_e2e():
10793
)
10894

10995
job_name = f"instance-prefs-proc-integ-{uuid.uuid4().hex[:8]}"
110-
try:
111-
processor.run(wait=False, logs=False, job_name=job_name)
112-
except Exception as e: # botocore ClientError
113-
message = str(e)
114-
if "ValidationException" in message and (
115-
"instanceType' failed to satisfy constraint: Member must not be null" in message
116-
or "InstanceType and InstanceCount greater than 0 must be specified" in message
117-
):
118-
# The public processing model still carries @required on
119-
# InstanceType/InstanceCount (the relaxation is bundled with the
120-
# GA/Trebuchet ungating), so a preferences-only create is rejected
121-
# by the frontend model validation on this endpoint. The SDK-side
122-
# request construction and serialization succeeded.
123-
pytest.xfail(
124-
"Server rollout incomplete on this endpoint: CreateProcessingJob "
125-
"rejected a preferences-only ClusterConfig (public model @required "
126-
"relaxation on InstanceType/InstanceCount not yet deployed): "
127-
f"{message[:200]}"
128-
)
129-
raise
96+
processor.run(wait=False, logs=False, job_name=job_name)
13097

13198
# --- Create accepted; Describe echoes the request contract -------------
13299
described = client.describe_processing_job(ProcessingJobName=job_name)
@@ -152,12 +119,6 @@ def test_processor_instance_preferences_e2e():
152119
time.sleep(POLL_SECONDS)
153120

154121
failure_reason = described.get("FailureReason", "")
155-
if status == "Failed" and "InternalServerError" in failure_reason:
156-
pytest.xfail(
157-
"Server rollout incomplete on this endpoint: processing job accepted "
158-
"with InstancePreferences but failed with InternalServerError before "
159-
f"type resolution (job={job_name})"
160-
)
161122

162123
# --- Full contract: resolved winner is surfaced and is a submitted pref -
163124
cluster_config = described["ProcessingResources"]["ClusterConfig"]

‎sagemaker-train/tests/integ/train/test_instance_preferences.py‎

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,10 @@
3030
- ``INSTANCE_PREFERENCES_TEST_IMAGE_URI`` - training image the account can pull
3131
- ``INSTANCE_PREFERENCES_TEST_S3_OUTPUT`` - s3:// output path
3232
- ``INSTANCE_PREFERENCES_TEST_S3_INPUT`` - optional s3:// input channel
33-
- ``SAGEMAKER_ENDPOINT`` - optional endpoint override (e.g.
34-
a pre-GA stage endpoint); honored by the sagemaker-core client loader.
35-
36-
Rollout note: the server-side feature ships in stages (validation ->
37-
scheduling sweep -> resolved-type write-back). While the scheduling stage is
38-
not yet deployed to the target endpoint, an accepted job fails with
39-
``InternalServerError`` before an instance type is resolved; the test detects
40-
that specific signature and reports it as XFAIL (server rollout incomplete)
41-
instead of a test failure, so it starts enforcing the full contract
42-
automatically once the stage deploys.
33+
- ``INSTANCE_PREFERENCES_TEST_INSTANCE_TYPES`` - optional comma-separated
34+
candidate types (default: ml.m5.xlarge, ml.m4.xlarge)
35+
- ``SAGEMAKER_ENDPOINT`` - optional endpoint override;
36+
honored by the sagemaker-core client loader.
4337
"""
4438

4539
from __future__ import absolute_import
@@ -57,7 +51,13 @@
5751
S3_OUTPUT = os.environ.get("INSTANCE_PREFERENCES_TEST_S3_OUTPUT")
5852
S3_INPUT = os.environ.get("INSTANCE_PREFERENCES_TEST_S3_INPUT")
5953

60-
PREFERENCE_TYPES = ["ml.m5.xlarge", "ml.m4.xlarge"]
54+
PREFERENCE_TYPES = [
55+
t.strip()
56+
for t in os.environ.get(
57+
"INSTANCE_PREFERENCES_TEST_INSTANCE_TYPES", "ml.m5.xlarge,ml.m4.xlarge"
58+
).split(",")
59+
if t.strip()
60+
]
6161
WAIT_TIMEOUT_SECONDS = 30 * 60
6262
POLL_SECONDS = 30
6363

@@ -118,14 +118,9 @@ def test_model_trainer_instance_preferences_e2e():
118118
echoed = described.resource_config
119119
# Top-level instance type was never set; must not come back populated.
120120
assert _get_value(echoed.instance_type) is None
121-
if _get_value(echoed.instance_preferences) is None:
122-
# Gated (pre-GA) endpoints strip instance_preferences/selected_* from
123-
# public Describe responses; the full contract is assertable post-ungate.
124-
pytest.xfail(
125-
"Dark-launch gating active on this endpoint: instance_preferences/"
126-
"selected_* are stripped from the public Describe response "
127-
f"(job={job_name}). Re-run after the GA ungate."
128-
)
121+
assert [p.instance_type for p in _get_value(echoed.instance_preferences) or []] == (
122+
PREFERENCE_TYPES
123+
), f"Describe did not echo instance_preferences (job={job_name})"
129124

130125
# Wait until terminal or winner visible (Selected* propagation lags the
131126
# secondary-status transitions; gate on the winner, not on secondary).
@@ -141,24 +136,15 @@ def test_model_trainer_instance_preferences_e2e():
141136
time.sleep(POLL_SECONDS)
142137

143138
failure_reason = _get_value(described.failure_reason) or ""
144-
if status == "Failed" and "InternalServerError" in failure_reason:
145-
# Accepted by validation but failed before type resolution: the
146-
# scheduling stage of the server rollout is not deployed on this
147-
# endpoint yet. Everything client-side (model, serialization,
148-
# acceptance, describe echo) has been asserted above.
149-
pytest.xfail(
150-
"Server rollout incomplete on this endpoint: job accepted with "
151-
"instance_preferences but failed with InternalServerError before "
152-
f"type resolution (job={job_name})"
153-
)
154139

155140
# --- Full contract: resolved winner is surfaced and is a submitted pref -
156141
final_rc = described.resource_config
157142
selected_type = _get_value(final_rc.selected_instance_type)
158143
selected_count = _get_value(final_rc.selected_instance_count)
159144
assert selected_type in PREFERENCE_TYPES, (
160145
f"selected_instance_type={selected_type!r} not among submitted "
161-
f"preferences {PREFERENCE_TYPES} (job={job_name}, status={status})"
146+
f"preferences {PREFERENCE_TYPES} (job={job_name}, status={status}, "
147+
f"failure={failure_reason!r})"
162148
)
163149
assert selected_count == 1
164150
assert _get_value(final_rc.instance_type) is None

0 commit comments

Comments
 (0)