|
21 | 21 | import pytest |
22 | 22 | from leptonai.api.v1.types.common import LeptonVisibility, Metadata |
23 | 23 | from leptonai.api.v1.types.deployment import ( |
| 24 | + EnvValue, |
| 25 | + EnvVar, |
24 | 26 | LeptonContainer, |
25 | 27 | LeptonResourceAffinity, |
26 | 28 | Mount, |
27 | | - EnvVar, |
28 | | - EnvValue, |
| 29 | + QueueConfig, |
29 | 30 | ) |
30 | 31 | from leptonai.api.v1.types.job import LeptonJob, LeptonJobUserSpec |
31 | 32 |
|
@@ -486,6 +487,145 @@ def test_create_lepton_job_with_empty_reservation_config(self, mock_APIClient_cl |
486 | 487 | created_job = mock_client.job.create.call_args[0][0] |
487 | 488 | assert created_job.spec.reservation_config is None |
488 | 489 |
|
| 490 | + def test_init_preemption_defaults(self): |
| 491 | + """Test that preemption fields default to off.""" |
| 492 | + executor = LeptonExecutor( |
| 493 | + container_image="test-image", |
| 494 | + nemo_run_dir="/test/path", |
| 495 | + mounts=[{"path": "/test", "mount_path": "/test"}], |
| 496 | + ) |
| 497 | + |
| 498 | + assert executor.can_be_preempted is False |
| 499 | + assert executor.can_preempt is False |
| 500 | + assert executor.queue_priority is None |
| 501 | + |
| 502 | + def test_init_with_can_be_preempted(self): |
| 503 | + executor = LeptonExecutor( |
| 504 | + container_image="test-image", |
| 505 | + nemo_run_dir="/test/path", |
| 506 | + mounts=[{"path": "/test", "mount_path": "/test"}], |
| 507 | + can_be_preempted=True, |
| 508 | + ) |
| 509 | + |
| 510 | + assert executor.can_be_preempted is True |
| 511 | + |
| 512 | + def test_init_with_can_preempt(self): |
| 513 | + executor = LeptonExecutor( |
| 514 | + container_image="test-image", |
| 515 | + nemo_run_dir="/test/path", |
| 516 | + mounts=[{"path": "/test", "mount_path": "/test"}], |
| 517 | + can_preempt=True, |
| 518 | + ) |
| 519 | + |
| 520 | + assert executor.can_preempt is True |
| 521 | + |
| 522 | + def test_init_with_queue_priority(self): |
| 523 | + executor = LeptonExecutor( |
| 524 | + container_image="test-image", |
| 525 | + nemo_run_dir="/test/path", |
| 526 | + mounts=[{"path": "/test", "mount_path": "/test"}], |
| 527 | + can_be_preempted=True, |
| 528 | + queue_priority="high-8000", |
| 529 | + ) |
| 530 | + |
| 531 | + assert executor.queue_priority == "high-8000" |
| 532 | + |
| 533 | + @patch("nemo_run.core.execution.lepton.APIClient") |
| 534 | + def test_create_lepton_job_without_preemption(self, mock_APIClient_class): |
| 535 | + """Test queue_config is None when neither preemption flag is set.""" |
| 536 | + mock_client = mock_APIClient_class.return_value |
| 537 | + mock_client.job.create.return_value = LeptonJob(metadata=Metadata(id="my-lepton-job")) |
| 538 | + node_group = SimpleNamespace(metadata=SimpleNamespace(id_="123456")) |
| 539 | + |
| 540 | + executor = LeptonExecutor( |
| 541 | + container_image="test-image", |
| 542 | + nemo_run_dir="/test/path", |
| 543 | + node_group="123456", |
| 544 | + mounts=[{"path": "/test", "mount_path": "/test"}], |
| 545 | + ) |
| 546 | + executor._valid_node_ids = MagicMock(return_value=["node-id-1"]) |
| 547 | + executor._node_group_id = MagicMock(return_value=node_group) |
| 548 | + |
| 549 | + executor.create_lepton_job("my-lepton-job") |
| 550 | + |
| 551 | + created_job = mock_client.job.create.call_args[0][0] |
| 552 | + assert created_job.spec.queue_config is None |
| 553 | + |
| 554 | + @patch("nemo_run.core.execution.lepton.APIClient") |
| 555 | + def test_create_lepton_job_with_can_be_preempted(self, mock_APIClient_class): |
| 556 | + """Test queue_config is set correctly when can_be_preempted=True.""" |
| 557 | + mock_client = mock_APIClient_class.return_value |
| 558 | + mock_client.job.create.return_value = LeptonJob(metadata=Metadata(id="my-lepton-job")) |
| 559 | + node_group = SimpleNamespace(metadata=SimpleNamespace(id_="123456")) |
| 560 | + |
| 561 | + executor = LeptonExecutor( |
| 562 | + container_image="test-image", |
| 563 | + nemo_run_dir="/test/path", |
| 564 | + node_group="123456", |
| 565 | + mounts=[{"path": "/test", "mount_path": "/test"}], |
| 566 | + can_be_preempted=True, |
| 567 | + ) |
| 568 | + executor._valid_node_ids = MagicMock(return_value=["node-id-1"]) |
| 569 | + executor._node_group_id = MagicMock(return_value=node_group) |
| 570 | + |
| 571 | + executor.create_lepton_job("my-lepton-job") |
| 572 | + |
| 573 | + created_job = mock_client.job.create.call_args[0][0] |
| 574 | + assert created_job.spec.queue_config == QueueConfig( |
| 575 | + priority_class="mid-4000", |
| 576 | + can_be_preempted=True, |
| 577 | + can_preempt=None, |
| 578 | + ) |
| 579 | + |
| 580 | + @patch("nemo_run.core.execution.lepton.APIClient") |
| 581 | + def test_create_lepton_job_with_can_preempt(self, mock_APIClient_class): |
| 582 | + """Test queue_config is set correctly when can_preempt=True.""" |
| 583 | + mock_client = mock_APIClient_class.return_value |
| 584 | + mock_client.job.create.return_value = LeptonJob(metadata=Metadata(id="my-lepton-job")) |
| 585 | + node_group = SimpleNamespace(metadata=SimpleNamespace(id_="123456")) |
| 586 | + |
| 587 | + executor = LeptonExecutor( |
| 588 | + container_image="test-image", |
| 589 | + nemo_run_dir="/test/path", |
| 590 | + node_group="123456", |
| 591 | + mounts=[{"path": "/test", "mount_path": "/test"}], |
| 592 | + can_preempt=True, |
| 593 | + ) |
| 594 | + executor._valid_node_ids = MagicMock(return_value=["node-id-1"]) |
| 595 | + executor._node_group_id = MagicMock(return_value=node_group) |
| 596 | + |
| 597 | + executor.create_lepton_job("my-lepton-job") |
| 598 | + |
| 599 | + created_job = mock_client.job.create.call_args[0][0] |
| 600 | + assert created_job.spec.queue_config == QueueConfig( |
| 601 | + priority_class="mid-4000", |
| 602 | + can_be_preempted=None, |
| 603 | + can_preempt=True, |
| 604 | + ) |
| 605 | + |
| 606 | + @patch("nemo_run.core.execution.lepton.APIClient") |
| 607 | + def test_create_lepton_job_with_custom_queue_priority(self, mock_APIClient_class): |
| 608 | + """Test that a custom queue_priority overrides the 'mid-4000' default.""" |
| 609 | + mock_client = mock_APIClient_class.return_value |
| 610 | + mock_client.job.create.return_value = LeptonJob(metadata=Metadata(id="my-lepton-job")) |
| 611 | + node_group = SimpleNamespace(metadata=SimpleNamespace(id_="123456")) |
| 612 | + |
| 613 | + executor = LeptonExecutor( |
| 614 | + container_image="test-image", |
| 615 | + nemo_run_dir="/test/path", |
| 616 | + node_group="123456", |
| 617 | + mounts=[{"path": "/test", "mount_path": "/test"}], |
| 618 | + can_be_preempted=True, |
| 619 | + queue_priority="high-8000", |
| 620 | + ) |
| 621 | + executor._valid_node_ids = MagicMock(return_value=["node-id-1"]) |
| 622 | + executor._node_group_id = MagicMock(return_value=node_group) |
| 623 | + |
| 624 | + executor.create_lepton_job("my-lepton-job") |
| 625 | + |
| 626 | + created_job = mock_client.job.create.call_args[0][0] |
| 627 | + assert created_job.spec.queue_config.priority_class == "high-8000" |
| 628 | + |
489 | 629 | def test_nnodes(self): |
490 | 630 | executor = LeptonExecutor( |
491 | 631 | container_image="nvcr.io/nvidia/test:latest", |
@@ -549,7 +689,11 @@ def test_valid_storage_mounts_with_mount_from(self): |
549 | 689 | container_image="nvcr.io/nvidia/test:latest", |
550 | 690 | nemo_run_dir="/workspace/nemo_run", |
551 | 691 | mounts=[ |
552 | | - {"path": "/workspace", "mount_path": "/workspace", "from": "local-storage:nfs"} |
| 692 | + { |
| 693 | + "path": "/workspace", |
| 694 | + "mount_path": "/workspace", |
| 695 | + "from": "local-storage:nfs", |
| 696 | + } |
553 | 697 | ], |
554 | 698 | ) |
555 | 699 |
|
@@ -730,7 +874,10 @@ def test_package_configs(self, mock_file, mock_makedirs): |
730 | 874 | mounts=[{"path": "/test", "mount_path": "/test"}], |
731 | 875 | ) |
732 | 876 |
|
733 | | - configs = [("config1.yaml", "key: value"), ("subdir/config2.yaml", "another: config")] |
| 877 | + configs = [ |
| 878 | + ("config1.yaml", "key: value"), |
| 879 | + ("subdir/config2.yaml", "another: config"), |
| 880 | + ] |
734 | 881 |
|
735 | 882 | filenames = executor.package_configs(*configs) |
736 | 883 |
|
@@ -855,7 +1002,9 @@ def test_launch_method_comprehensive( |
855 | 1002 | """Test launch method name validation, pre_launch_commands, and script generation.""" |
856 | 1003 | # Setup |
857 | 1004 | executor = LeptonExecutor( |
858 | | - container_image="test-image", nemo_run_dir="/test", pre_launch_commands=["echo setup"] |
| 1005 | + container_image="test-image", |
| 1006 | + nemo_run_dir="/test", |
| 1007 | + pre_launch_commands=["echo setup"], |
859 | 1008 | ) |
860 | 1009 | executor.job_dir = executor.lepton_job_dir = "/fake" |
861 | 1010 | mock_join.return_value = "/fake/script.sh" |
|
0 commit comments