Skip to content

Commit e5a865c

Browse files
awaelchliotajBorda
authored
Validate the combination of CloudCompute and BuildConfig (#14929)
Co-authored-by: otaj <[email protected]> Co-authored-by: Jirka Borovec <[email protected]> Co-authored-by: Jirka <[email protected]>
1 parent 23f88cd commit e5a865c

File tree

3 files changed

+74
-38
lines changed

3 files changed

+74
-38
lines changed

src/lightning_app/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
1515
- Enabled MultiNode Components to support state broadcasting ([#15607](https://github.com/Lightning-AI/lightning/pull/15607))
1616

1717

18+
- Added a friendly error message when attempting to run the default cloud compute with a custom base image configured ([#14929](https://github.com/Lightning-AI/lightning/pull/14929))
19+
20+
1821
### Changed
1922

2023
-

src/lightning_app/runners/cloud.py

+12
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
)
4444
from lightning_cloud.openapi.rest import ApiException
4545

46+
from lightning_app import LightningWork
4647
from lightning_app.core.app import LightningApp
4748
from lightning_app.core.constants import (
4849
CLOUD_QUEUE_TYPE,
@@ -143,6 +144,8 @@ def dispatch(
143144

144145
works: List[V1Work] = []
145146
for work in self.app.works:
147+
_validate_build_spec_and_compute(work)
148+
146149
if not work._start_with_flow:
147150
continue
148151

@@ -519,3 +522,12 @@ def _create_mount_drive_spec(work_name: str, mount: Mount) -> V1LightningworkDri
519522
),
520523
mount_location=str(mount.mount_path),
521524
)
525+
526+
527+
def _validate_build_spec_and_compute(work: LightningWork) -> None:
528+
if work.cloud_build_config.image is not None and work.cloud_compute.name == "default":
529+
raise ValueError(
530+
f"You requested a custom base image for the Work with name '{work.name}', but custom images are currently"
531+
" not supported on the default cloud compute instance. Please choose a different configuration, for example"
532+
" `CloudCompute('cpu-medium')`."
533+
)

tests/tests_app/runners/test_cloud.py

+59-38
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@
3939
V1Work,
4040
)
4141

42-
from lightning_app import _PROJECT_ROOT, LightningApp, LightningWork
42+
from lightning_app import _PROJECT_ROOT, BuildConfig, LightningApp, LightningWork
4343
from lightning_app.runners import backends, cloud, CloudRuntime
44+
from lightning_app.runners.cloud import _validate_build_spec_and_compute
4445
from lightning_app.storage import Drive, Mount
4546
from lightning_app.testing.helpers import EmptyFlow
4647
from lightning_app.utilities.cloud import _get_project
@@ -54,17 +55,17 @@ def run(self):
5455

5556

5657
class WorkWithSingleDrive(LightningWork):
57-
def __init__(self):
58-
super().__init__()
58+
def __init__(self, *args, **kwargs):
59+
super().__init__(*args, **kwargs)
5960
self.drive = None
6061

6162
def run(self):
6263
pass
6364

6465

6566
class WorkWithTwoDrives(LightningWork):
66-
def __init__(self):
67-
super().__init__()
67+
def __init__(self, *args, **kwargs):
68+
super().__init__(*args, **kwargs)
6869
self.lit_drive_1 = None
6970
self.lit_drive_2 = None
7071

@@ -403,13 +404,13 @@ def test_call_with_work_app(self, lightningapps, start_with_flow, monkeypatch, t
403404
monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
404405
app = mock.MagicMock()
405406

406-
work = MyWork(start_with_flow=start_with_flow)
407-
monkeypatch.setattr(work, "_name", "test-work")
408-
monkeypatch.setattr(work._cloud_build_config, "build_commands", lambda: ["echo 'start'"])
409-
monkeypatch.setattr(work._cloud_build_config, "requirements", ["torch==1.0.0", "numpy==1.0.0"])
410-
monkeypatch.setattr(work._cloud_build_config, "image", "random_base_public_image")
411-
monkeypatch.setattr(work._cloud_compute, "disk_size", 0)
412-
monkeypatch.setattr(work, "_port", 8080)
407+
work = MyWork(start_with_flow=start_with_flow, cloud_compute=CloudCompute("custom"))
408+
work._name = "test-work"
409+
work._cloud_build_config.build_commands = lambda: ["echo 'start'"]
410+
work._cloud_build_config.requirements = ["torch==1.0.0", "numpy==1.0.0"]
411+
work._cloud_build_config.image = "random_base_public_image"
412+
work._cloud_compute.disk_size = 0
413+
work._port = 8080
413414

414415
app.works = [work]
415416
cloud_runtime = cloud.CloudRuntime(app=app, entrypoint_file=(source_code_root_dir / "entrypoint.py"))
@@ -450,7 +451,7 @@ def test_call_with_work_app(self, lightningapps, start_with_flow, monkeypatch, t
450451
),
451452
drives=[],
452453
user_requested_compute_config=V1UserRequestedComputeConfig(
453-
name="default",
454+
name="custom",
454455
count=1,
455456
disk_size=0,
456457
shm_size=0,
@@ -585,7 +586,7 @@ def test_call_with_work_app_and_attached_drives(self, lightningapps, monkeypatch
585586
# should be the results of the deepcopy operation (an instance of the original class)
586587
mocked_drive.__deepcopy__.return_value = copy(mocked_drive)
587588

588-
work = WorkWithSingleDrive()
589+
work = WorkWithSingleDrive(cloud_compute=CloudCompute("custom"))
589590
monkeypatch.setattr(work, "drive", mocked_drive)
590591
monkeypatch.setattr(work, "_state", {"_port", "drive"})
591592
monkeypatch.setattr(work, "_name", "test-work")
@@ -646,7 +647,7 @@ def test_call_with_work_app_and_attached_drives(self, lightningapps, monkeypatch
646647
),
647648
],
648649
user_requested_compute_config=V1UserRequestedComputeConfig(
649-
name="default",
650+
name="custom",
650651
count=1,
651652
disk_size=0,
652653
shm_size=0,
@@ -709,14 +710,14 @@ def test_call_with_work_app_and_app_comment_command_execution_set(self, lightnin
709710
monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", mock.MagicMock())
710711
app = mock.MagicMock()
711712

712-
work = MyWork()
713-
monkeypatch.setattr(work, "_state", {"_port"})
714-
monkeypatch.setattr(work, "_name", "test-work")
715-
monkeypatch.setattr(work._cloud_build_config, "build_commands", lambda: ["echo 'start'"])
716-
monkeypatch.setattr(work._cloud_build_config, "requirements", ["torch==1.0.0", "numpy==1.0.0"])
717-
monkeypatch.setattr(work._cloud_build_config, "image", "random_base_public_image")
718-
monkeypatch.setattr(work._cloud_compute, "disk_size", 0)
719-
monkeypatch.setattr(work, "_port", 8080)
713+
work = MyWork(cloud_compute=CloudCompute("custom"))
714+
work._state = {"_port"}
715+
work._name = "test-work"
716+
work._cloud_build_config.build_commands = lambda: ["echo 'start'"]
717+
work._cloud_build_config.requirements = ["torch==1.0.0", "numpy==1.0.0"]
718+
work._cloud_build_config.image = "random_base_public_image"
719+
work._cloud_compute.disk_size = 0
720+
work._port = 8080
720721

721722
app.works = [work]
722723
cloud_runtime = cloud.CloudRuntime(app=app, entrypoint_file=(source_code_root_dir / "entrypoint.py"))
@@ -755,7 +756,7 @@ def test_call_with_work_app_and_app_comment_command_execution_set(self, lightnin
755756
),
756757
drives=[],
757758
user_requested_compute_config=V1UserRequestedComputeConfig(
758-
name="default", count=1, disk_size=0, shm_size=0, preemptible=mock.ANY
759+
name="custom", count=1, disk_size=0, shm_size=0, preemptible=mock.ANY
759760
),
760761
network_config=[V1NetworkConfig(name=mock.ANY, host=None, port=8080)],
761762
cluster_id=mock.ANY,
@@ -835,16 +836,16 @@ def test_call_with_work_app_and_multiple_attached_drives(self, lightningapps, mo
835836
# should be the results of the deepcopy operation (an instance of the original class)
836837
mocked_lit_drive.__deepcopy__.return_value = copy(mocked_lit_drive)
837838

838-
work = WorkWithTwoDrives()
839-
monkeypatch.setattr(work, "lit_drive_1", mocked_lit_drive)
840-
monkeypatch.setattr(work, "lit_drive_2", mocked_lit_drive)
841-
monkeypatch.setattr(work, "_state", {"_port", "_name", "lit_drive_1", "lit_drive_2"})
842-
monkeypatch.setattr(work, "_name", "test-work")
843-
monkeypatch.setattr(work._cloud_build_config, "build_commands", lambda: ["echo 'start'"])
844-
monkeypatch.setattr(work._cloud_build_config, "requirements", ["torch==1.0.0", "numpy==1.0.0"])
845-
monkeypatch.setattr(work._cloud_build_config, "image", "random_base_public_image")
846-
monkeypatch.setattr(work._cloud_compute, "disk_size", 0)
847-
monkeypatch.setattr(work, "_port", 8080)
839+
work = WorkWithTwoDrives(cloud_compute=CloudCompute("custom"))
840+
work.lit_drive_1 = mocked_lit_drive
841+
work.lit_drive_2 = mocked_lit_drive
842+
work._state = {"_port", "_name", "lit_drive_1", "lit_drive_2"}
843+
work._name = "test-work"
844+
work._cloud_build_config.build_commands = lambda: ["echo 'start'"]
845+
work._cloud_build_config.requirements = ["torch==1.0.0", "numpy==1.0.0"]
846+
work._cloud_build_config.image = "random_base_public_image"
847+
work._cloud_compute.disk_size = 0
848+
work._port = 8080
848849

849850
app.works = [work]
850851
cloud_runtime = cloud.CloudRuntime(app=app, entrypoint_file=(source_code_root_dir / "entrypoint.py"))
@@ -914,7 +915,7 @@ def test_call_with_work_app_and_multiple_attached_drives(self, lightningapps, mo
914915
),
915916
drives=[lit_drive_2_spec, lit_drive_1_spec],
916917
user_requested_compute_config=V1UserRequestedComputeConfig(
917-
name="default",
918+
name="custom",
918919
count=1,
919920
disk_size=0,
920921
shm_size=0,
@@ -953,7 +954,7 @@ def test_call_with_work_app_and_multiple_attached_drives(self, lightningapps, mo
953954
),
954955
drives=[lit_drive_1_spec, lit_drive_2_spec],
955956
user_requested_compute_config=V1UserRequestedComputeConfig(
956-
name="default",
957+
name="custom",
957958
count=1,
958959
disk_size=0,
959960
shm_size=0,
@@ -1043,7 +1044,7 @@ def test_call_with_work_app_and_attached_mount_and_drive(self, lightningapps, mo
10431044
setattr(mocked_mount, "mount_path", "/content/foo")
10441045
setattr(mocked_mount, "protocol", "s3://")
10451046

1046-
work = WorkWithSingleDrive()
1047+
work = WorkWithSingleDrive(cloud_compute=CloudCompute("custom"))
10471048
monkeypatch.setattr(work, "drive", mocked_drive)
10481049
monkeypatch.setattr(work, "_state", {"_port", "drive"})
10491050
monkeypatch.setattr(work, "_name", "test-work")
@@ -1119,7 +1120,7 @@ def test_call_with_work_app_and_attached_mount_and_drive(self, lightningapps, mo
11191120
),
11201121
],
11211122
user_requested_compute_config=V1UserRequestedComputeConfig(
1122-
name="default",
1123+
name="custom",
11231124
count=1,
11241125
disk_size=0,
11251126
shm_size=0,
@@ -1227,3 +1228,23 @@ def test_load_app_from_file_module_error():
12271228
empty_app = CloudRuntime.load_app_from_file(os.path.join(_PROJECT_ROOT, "examples", "app_v0", "app.py"))
12281229
assert isinstance(empty_app, LightningApp)
12291230
assert isinstance(empty_app.root, EmptyFlow)
1231+
1232+
1233+
def test_incompatible_cloud_compute_and_build_config():
1234+
"""Test that an exception is raised when a build config has a custom image defined, but the cloud compute is
1235+
the default.
1236+
1237+
This combination is not supported by the platform.
1238+
"""
1239+
1240+
class Work(LightningWork):
1241+
def __init__(self):
1242+
super().__init__()
1243+
self.cloud_compute = CloudCompute(name="default")
1244+
self.cloud_build_config = BuildConfig(image="custom")
1245+
1246+
def run(self):
1247+
pass
1248+
1249+
with pytest.raises(ValueError, match="You requested a custom base image for the Work with name"):
1250+
_validate_build_spec_and_compute(Work())

0 commit comments

Comments
 (0)