|
22 | 22 | get_volume_provisioning_data, |
23 | 23 | list_events, |
24 | 24 | ) |
| 25 | +from dstack._internal.settings import FeatureFlags |
25 | 26 | from dstack._internal.utils.common import get_current_datetime |
26 | 27 |
|
27 | 28 |
|
| 29 | +@pytest.fixture |
| 30 | +def patch_pipeline_processing_flag(monkeypatch: pytest.MonkeyPatch): |
| 31 | + def _apply(enabled: bool): |
| 32 | + monkeypatch.setattr(FeatureFlags, "PIPELINE_PROCESSING_ENABLED", enabled) |
| 33 | + |
| 34 | + return _apply |
| 35 | + |
| 36 | + |
28 | 37 | @pytest.mark.asyncio |
29 | 38 | @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True) |
30 | | -class TestProcessIdleVolumes: |
| 39 | +class TestProcessIdleVolumesScheduledTask: |
| 40 | + @pytest.fixture(autouse=True) |
| 41 | + def _patch_feature_flag(self, patch_pipeline_processing_flag): |
| 42 | + patch_pipeline_processing_flag(False) |
| 43 | + |
31 | 44 | async def test_deletes_idle_volumes(self, test_db, session: AsyncSession): |
32 | 45 | project = await create_project(session=session) |
33 | 46 | user = await create_user(session=session) |
@@ -71,17 +84,169 @@ async def test_deletes_idle_volumes(self, test_db, session: AsyncSession): |
71 | 84 | m.return_value = aws_mock |
72 | 85 | aws_mock.compute.return_value = Mock(spec=ComputeMockSpec) |
73 | 86 | await process_idle_volumes() |
| 87 | + m.assert_called_once() |
74 | 88 |
|
75 | 89 | await session.refresh(volume1) |
76 | 90 | await session.refresh(volume2) |
77 | 91 | events = await list_events(session) |
| 92 | + assert not volume1.to_be_deleted |
78 | 93 | assert volume1.deleted |
79 | 94 | assert volume1.deleted_at is not None |
| 95 | + assert not volume2.to_be_deleted |
80 | 96 | assert not volume2.deleted |
81 | 97 | assert volume2.deleted_at is None |
82 | 98 | assert len(events) == 1 |
83 | 99 | assert events[0].message == "Volume deleted due to exceeding auto_cleanup_duration" |
84 | 100 |
|
| 101 | + async def test_deletes_idle_volume_with_null_auto_cleanup_enabled( |
| 102 | + self, test_db, session: AsyncSession |
| 103 | + ): |
| 104 | + project = await create_project(session=session) |
| 105 | + user = await create_user(session=session) |
| 106 | + volume = await create_volume( |
| 107 | + session=session, |
| 108 | + project=project, |
| 109 | + user=user, |
| 110 | + status=VolumeStatus.ACTIVE, |
| 111 | + backend=BackendType.AWS, |
| 112 | + configuration=get_volume_configuration( |
| 113 | + name="test-volume", |
| 114 | + auto_cleanup_duration="1h", |
| 115 | + ), |
| 116 | + volume_provisioning_data=get_volume_provisioning_data(), |
| 117 | + last_job_processed_at=datetime.datetime.now(datetime.timezone.utc) |
| 118 | + - datetime.timedelta(hours=2), |
| 119 | + ) |
| 120 | + volume.auto_cleanup_enabled = None |
| 121 | + await session.commit() |
| 122 | + |
| 123 | + with patch( |
| 124 | + "dstack._internal.server.services.backends.get_project_backend_by_type_or_error" |
| 125 | + ) as m: |
| 126 | + aws_mock = Mock() |
| 127 | + m.return_value = aws_mock |
| 128 | + aws_mock.compute.return_value = Mock(spec=ComputeMockSpec) |
| 129 | + await process_idle_volumes() |
| 130 | + m.assert_called_once() |
| 131 | + |
| 132 | + await session.refresh(volume) |
| 133 | + events = await list_events(session) |
| 134 | + assert not volume.to_be_deleted |
| 135 | + assert volume.deleted |
| 136 | + assert volume.deleted_at is not None |
| 137 | + assert len(events) == 1 |
| 138 | + assert events[0].message == "Volume deleted due to exceeding auto_cleanup_duration" |
| 139 | + |
| 140 | + |
| 141 | +@pytest.mark.asyncio |
| 142 | +@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True) |
| 143 | +class TestProcessIdleVolumesPipelineTask: |
| 144 | + @pytest.fixture(autouse=True) |
| 145 | + def _patch_feature_flag(self, patch_pipeline_processing_flag): |
| 146 | + patch_pipeline_processing_flag(True) |
| 147 | + |
| 148 | + async def test_deletes_idle_volumes(self, test_db, session: AsyncSession): |
| 149 | + project = await create_project(session=session) |
| 150 | + user = await create_user(session=session) |
| 151 | + |
| 152 | + config1 = get_volume_configuration( |
| 153 | + name="test-volume", |
| 154 | + auto_cleanup_duration="1h", |
| 155 | + ) |
| 156 | + config2 = get_volume_configuration( |
| 157 | + name="test-volume", |
| 158 | + auto_cleanup_duration="3h", |
| 159 | + ) |
| 160 | + volume1 = await create_volume( |
| 161 | + session=session, |
| 162 | + project=project, |
| 163 | + user=user, |
| 164 | + status=VolumeStatus.ACTIVE, |
| 165 | + backend=BackendType.AWS, |
| 166 | + configuration=config1, |
| 167 | + volume_provisioning_data=get_volume_provisioning_data(), |
| 168 | + last_job_processed_at=datetime.datetime.now(datetime.timezone.utc) |
| 169 | + - datetime.timedelta(hours=2), |
| 170 | + ) |
| 171 | + volume2 = await create_volume( |
| 172 | + session=session, |
| 173 | + project=project, |
| 174 | + user=user, |
| 175 | + status=VolumeStatus.ACTIVE, |
| 176 | + backend=BackendType.AWS, |
| 177 | + configuration=config2, |
| 178 | + volume_provisioning_data=get_volume_provisioning_data(), |
| 179 | + last_job_processed_at=datetime.datetime.now(datetime.timezone.utc) |
| 180 | + - datetime.timedelta(hours=2), |
| 181 | + ) |
| 182 | + await session.commit() |
| 183 | + |
| 184 | + with patch( |
| 185 | + "dstack._internal.server.services.backends.get_project_backend_by_type_or_error" |
| 186 | + ) as m: |
| 187 | + aws_mock = Mock() |
| 188 | + m.return_value = aws_mock |
| 189 | + aws_mock.compute.return_value = Mock(spec=ComputeMockSpec) |
| 190 | + await process_idle_volumes() |
| 191 | + m.assert_not_called() |
| 192 | + |
| 193 | + await session.refresh(volume1) |
| 194 | + await session.refresh(volume2) |
| 195 | + events = await list_events(session) |
| 196 | + assert volume1.to_be_deleted |
| 197 | + assert not volume1.deleted |
| 198 | + assert volume1.deleted_at is None |
| 199 | + assert not volume2.to_be_deleted |
| 200 | + assert not volume2.deleted |
| 201 | + assert volume2.deleted_at is None |
| 202 | + assert len(events) == 1 |
| 203 | + assert ( |
| 204 | + events[0].message |
| 205 | + == "Volume marked for deletion due to exceeding auto_cleanup_duration" |
| 206 | + ) |
| 207 | + |
| 208 | + async def test_deletes_idle_volume_with_null_auto_cleanup_enabled( |
| 209 | + self, test_db, session: AsyncSession |
| 210 | + ): |
| 211 | + project = await create_project(session=session) |
| 212 | + user = await create_user(session=session) |
| 213 | + volume = await create_volume( |
| 214 | + session=session, |
| 215 | + project=project, |
| 216 | + user=user, |
| 217 | + status=VolumeStatus.ACTIVE, |
| 218 | + backend=BackendType.AWS, |
| 219 | + configuration=get_volume_configuration( |
| 220 | + name="test-volume", |
| 221 | + auto_cleanup_duration="1h", |
| 222 | + ), |
| 223 | + volume_provisioning_data=get_volume_provisioning_data(), |
| 224 | + last_job_processed_at=datetime.datetime.now(datetime.timezone.utc) |
| 225 | + - datetime.timedelta(hours=2), |
| 226 | + ) |
| 227 | + volume.auto_cleanup_enabled = None |
| 228 | + await session.commit() |
| 229 | + |
| 230 | + with patch( |
| 231 | + "dstack._internal.server.services.backends.get_project_backend_by_type_or_error" |
| 232 | + ) as m: |
| 233 | + aws_mock = Mock() |
| 234 | + m.return_value = aws_mock |
| 235 | + aws_mock.compute.return_value = Mock(spec=ComputeMockSpec) |
| 236 | + await process_idle_volumes() |
| 237 | + m.assert_not_called() |
| 238 | + |
| 239 | + await session.refresh(volume) |
| 240 | + events = await list_events(session) |
| 241 | + assert volume.to_be_deleted |
| 242 | + assert not volume.deleted |
| 243 | + assert volume.deleted_at is None |
| 244 | + assert len(events) == 1 |
| 245 | + assert ( |
| 246 | + events[0].message |
| 247 | + == "Volume marked for deletion due to exceeding auto_cleanup_duration" |
| 248 | + ) |
| 249 | + |
85 | 250 |
|
86 | 251 | @pytest.mark.asyncio |
87 | 252 | @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True) |
|
0 commit comments