Skip to content

Commit 18c4e39

Browse files
committed
Check if a dataset is attached to a runner on dataset deletion
- Introduced methods to associate dataset parameters with runners (`findRunnerByDatasetParameter`) in `RunnerService`. - Added event `GetAttachedRunnerToDataset` for validating dataset usage during deletion. - Updated documentation and OpenAPI spec with indexed `dataset` field representation. - Implemented new integration tests for dataset usage validation and deletion constraints.
1 parent aa69614 commit 18c4e39

File tree

9 files changed

+151
-45
lines changed

9 files changed

+151
-45
lines changed

common/src/main/kotlin/com/cosmotech/common/events/RunnerEvents.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,6 @@ class TriggerRunnerEvent(
99
val runnerId: String
1010
) : CsmRequestResponseEvent<String>(publisher)
1111

12-
class AskRunnerStatusEvent(
13-
publisher: Any,
14-
val organizationId: String,
15-
val workspaceId: String,
16-
val runnerId: String
17-
) : CsmRequestResponseEvent<String>(publisher)
18-
1912
class RunnerDeleted(
2013
publisher: Any,
2114
val organizationId: String,
@@ -30,3 +23,10 @@ class UpdateRunnerStatus(
3023
val runnerId: String,
3124
val lastRunId: String,
3225
) : CsmRequestResponseEvent<String>(publisher)
26+
27+
class GetAttachedRunnerToDataset(
28+
publisher: Any,
29+
val organizationId: String,
30+
val workspaceId: String,
31+
val datasetId: String
32+
) : CsmRequestResponseEvent<String>(publisher)

dataset/src/integrationTest/kotlin/com/cosmotech/dataset/service/DatasetServiceIntegrationTest.kt

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package com.cosmotech.dataset.service
44

55
import com.cosmotech.common.config.CsmPlatformProperties
66
import com.cosmotech.common.config.existTable
7+
import com.cosmotech.common.events.CsmEventPublisher
8+
import com.cosmotech.common.events.GetAttachedRunnerToDataset
79
import com.cosmotech.common.exceptions.CsmAccessForbiddenException
810
import com.cosmotech.common.exceptions.CsmResourceNotFoundException
911
import com.cosmotech.common.rbac.ROLE_ADMIN
@@ -45,6 +47,7 @@ import com.cosmotech.workspace.domain.WorkspaceAccessControl
4547
import com.cosmotech.workspace.domain.WorkspaceCreateRequest
4648
import com.cosmotech.workspace.domain.WorkspaceSecurity
4749
import com.cosmotech.workspace.domain.WorkspaceSolution
50+
import com.ninjasquad.springmockk.SpykBean
4851
import com.redis.om.spring.indexing.RediSearchIndexer
4952
import io.awspring.cloud.s3.S3Template
5053
import io.mockk.every
@@ -108,6 +111,7 @@ class DatasetServiceIntegrationTest() : CsmTestBase() {
108111
@Autowired lateinit var s3Template: S3Template
109112
@Autowired lateinit var resourceLoader: ResourceLoader
110113
@Autowired lateinit var writerJdbcTemplate: JdbcTemplate
114+
@SpykBean @Autowired private lateinit var eventPublisher: CsmEventPublisher
111115

112116
lateinit var organization: OrganizationCreateRequest
113117
lateinit var workspace: WorkspaceCreateRequest
@@ -702,6 +706,66 @@ class DatasetServiceIntegrationTest() : CsmTestBase() {
702706
assertFalse(s3Template.objectExists(csmPlatformProperties.s3.bucketName, fileKeyPath))
703707
}
704708

709+
@Test
710+
fun `test deleteDataset when its a Runner dataset parameter`() {
711+
712+
val datasetPartName = "Customers list"
713+
val datasetPartDescription = "List of customers"
714+
val datasetPartTags = mutableListOf("part", "public", "customers")
715+
val datasetPartCreateRequest =
716+
DatasetPartCreateRequest(
717+
name = datasetPartName,
718+
sourceName = CUSTOMER_SOURCE_FILE_NAME,
719+
description = datasetPartDescription,
720+
tags = datasetPartTags,
721+
type = DatasetPartTypeEnum.File)
722+
723+
val datasetName = "Customer Dataset"
724+
val datasetDescription = "Dataset for customers"
725+
val datasetTags = mutableListOf("dataset", "public", "customers")
726+
val datasetCreateRequest =
727+
DatasetCreateRequest(
728+
name = datasetName,
729+
description = datasetDescription,
730+
tags = datasetTags,
731+
parts = mutableListOf(datasetPartCreateRequest))
732+
733+
val resourceTestFile = resourceLoader.getResource("classpath:/$CUSTOMER_SOURCE_FILE_NAME").file
734+
735+
val fileToSend = FileInputStream(resourceTestFile)
736+
737+
val mockMultipartFile =
738+
MockMultipartFile(
739+
"files",
740+
CUSTOMER_SOURCE_FILE_NAME,
741+
MediaType.MULTIPART_FORM_DATA_VALUE,
742+
IOUtils.toByteArray(fileToSend))
743+
744+
val datasetId =
745+
datasetApiService
746+
.createDataset(
747+
organizationSaved.id,
748+
workspaceSaved.id,
749+
datasetCreateRequest,
750+
arrayOf(mockMultipartFile))
751+
.id
752+
753+
val fakeRunnerId = "r-XXXXXX"
754+
every { eventPublisher.publishEvent(any()) } answers
755+
{
756+
firstArg<GetAttachedRunnerToDataset>().response = fakeRunnerId
757+
}
758+
759+
val exception =
760+
assertThrows<IllegalArgumentException> {
761+
datasetApiService.deleteDataset(organizationSaved.id, workspaceSaved.id, datasetId)
762+
}
763+
764+
assertEquals(
765+
"Dataset $datasetId is defined as a runner dataset ($fakeRunnerId). It cannot be deleted",
766+
exception.message)
767+
}
768+
705769
@Test
706770
fun `test getDataset with no dataset part`() {
707771

dataset/src/main/kotlin/com/cosmotech/dataset/service/DatasetServiceImpl.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package com.cosmotech.dataset.service
44

55
import com.cosmotech.common.CsmPhoenixService
66
import com.cosmotech.common.config.DATASET_INPUTS_SCHEMA
7+
import com.cosmotech.common.events.GetAttachedRunnerToDataset
78
import com.cosmotech.common.exceptions.CsmResourceNotFoundException
89
import com.cosmotech.common.id.generateId
910
import com.cosmotech.common.rbac.CsmRbac
@@ -193,6 +194,17 @@ class DatasetServiceImpl(
193194

194195
override fun deleteDataset(organizationId: String, workspaceId: String, datasetId: String) {
195196
val dataset = getVerifiedDataset(organizationId, workspaceId, datasetId, PERMISSION_DELETE)
197+
198+
val getAttachedRunnerToDatasetEvent =
199+
GetAttachedRunnerToDataset(this, organizationId, workspaceId, datasetId)
200+
201+
eventPublisher.publishEvent(getAttachedRunnerToDatasetEvent)
202+
203+
val datasetAttachedToRunnerId = getAttachedRunnerToDatasetEvent.response
204+
require(datasetAttachedToRunnerId == null || datasetAttachedToRunnerId.isEmpty()) {
205+
"Dataset $datasetId is defined as a runner dataset ($datasetAttachedToRunnerId). It cannot be deleted"
206+
}
207+
196208
datasetRepository.delete(dataset)
197209
dataset.parts.forEach {
198210
datasetPartRepository.delete(it)

doc/Models/Runner.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
| **solutionName** | **String** | the Solution name | [optional] [default to null] |
1919
| **runTemplateName** | **String** | the Solution Run Template name associated with this Runner | [optional] [default to null] |
2020
| **additionalData** | [**Map**](AnyType.md) | Free form additional data | [optional] [default to null] |
21-
| **datasets** | [**RunnerDatasets**](RunnerDatasets.md) | | [default to null] |
21+
| **datasets** | [**RunnerDatasets**](RunnerDatasets.md) | definition of datasets used by the runner | [default to null] |
2222
| **runSizing** | [**RunnerResourceSizing**](RunnerResourceSizing.md) | | [optional] [default to null] |
2323
| **parametersValues** | [**List**](RunnerRunTemplateParameterValue.md) | the list of Solution Run Template parameters values | [default to null] |
2424
| **lastRunInfo** | [**LastRunInfo**](LastRunInfo.md) | | [default to null] |

runner/src/integrationTest/kotlin/com/cosmotech/runner/service/RunnerServiceIntegrationTest.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package com.cosmotech.runner.service
55
import com.cosmotech.common.config.CsmPlatformProperties
66
import com.cosmotech.common.containerregistry.ContainerRegistryService
77
import com.cosmotech.common.events.CsmEventPublisher
8+
import com.cosmotech.common.events.GetAttachedRunnerToDataset
89
import com.cosmotech.common.events.HasRunningRuns
910
import com.cosmotech.common.events.RunStart
1011
import com.cosmotech.common.events.UpdateRunnerStatus
@@ -2159,6 +2160,34 @@ class RunnerServiceIntegrationTest : CsmTestBase() {
21592160
assertEquals(0, datasetParameterFromChildRunner.parts.size)
21602161
}
21612162

2163+
@Test
2164+
fun `test onGetAttachedRunnerToDataset behaviour`() {
2165+
2166+
logger.info(
2167+
"should create a new Runner and retrieve parameter varType from solution ignoring the one declared")
2168+
val newRunner =
2169+
makeRunnerCreateRequest(
2170+
name = "NewRunner",
2171+
datasetList = mutableListOf(datasetSaved.id),
2172+
parametersValues =
2173+
mutableListOf(
2174+
RunnerRunTemplateParameterValue(
2175+
parameterId = "param1", value = "7", varType = "ignored_var_type")))
2176+
val newRunnerSaved =
2177+
runnerApiService.createRunner(organizationSaved.id, workspaceSaved.id, newRunner)
2178+
2179+
assertNotNull(newRunnerSaved)
2180+
assertNotNull(newRunnerSaved.datasets)
2181+
val datasetParameterId = newRunnerSaved.datasets.parameter
2182+
2183+
val getAttachedRunnerToDataset =
2184+
GetAttachedRunnerToDataset(
2185+
this, organizationSaved.id, workspaceSaved.id, datasetParameterId)
2186+
eventPublisher.publishEvent(getAttachedRunnerToDataset)
2187+
2188+
assertEquals(newRunnerSaved.id, getAttachedRunnerToDataset.response)
2189+
}
2190+
21622191
fun makeDataset(
21632192
name: String = "name",
21642193
): DatasetCreateRequest {

runner/src/main/kotlin/com/cosmotech/runner/repository/RunnerRepository.kt

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,6 @@ interface RunnerRepository : RedisDocumentRepository<Runner, String> {
2323
@Sanitize @Param("runnerId") runnerId: String
2424
): Optional<Runner>
2525

26-
@Query(
27-
"(@organizationId:{\$organizationId} @workspaceId:{\$workspaceId}) @validationStatus:\$validationStatus")
28-
fun findByValidationStatus(
29-
@Sanitize @Param("organizationId") organizationId: String,
30-
@Sanitize @Param("workspaceId") workspaceId: String,
31-
@Sanitize @Param("validationStatus") validationStatus: String,
32-
pageable: Pageable
33-
): Page<Runner>
34-
35-
@Query(
36-
"(@organizationId:{\$organizationId} @workspaceId:{\$workspaceId}) " +
37-
"@validationStatus:\$validationStatus \$securityConstraint")
38-
fun findByValidationStatusAndSecurity(
39-
@Sanitize @Param("organizationId") organizationId: String,
40-
@Sanitize @Param("workspaceId") workspaceId: String,
41-
@Sanitize @Param("validationStatus") validationStatus: String,
42-
@SecurityConstraint @Param("securityConstraint") securityConstraint: String,
43-
pageable: Pageable
44-
): Page<Runner>
45-
4626
@Query("(@organizationId:{\$organizationId} @workspaceId:{\$workspaceId} @parentId:{\$parentId})")
4727
fun findByParentId(
4828
@Sanitize @Param("organizationId") organizationId: String,
@@ -51,22 +31,6 @@ interface RunnerRepository : RedisDocumentRepository<Runner, String> {
5131
pageable: Pageable
5232
): Page<Runner>
5333

54-
@Query("(@organizationId:{\$organizationId})")
55-
fun findByOrganizationId(
56-
@Sanitize @Param("organizationId") organizationId: String,
57-
pageable: Pageable
58-
): Page<Runner>
59-
60-
@Query(
61-
"(@organizationId:{\$organizationId} @workspaceId:{\$workspaceId} @parentId:{\$parentId}) \$securityConstraint")
62-
fun findByParentIdAndSecurity(
63-
@Sanitize @Param("organizationId") organizationId: String,
64-
@Sanitize @Param("workspaceId") workspaceId: String,
65-
@Sanitize @Param("parentId") parentId: String,
66-
@SecurityConstraint @Param("securityConstraint") securityConstraint: String,
67-
pageable: Pageable
68-
): Page<Runner>
69-
7034
@Query("(@organizationId:{\$organizationId} @workspaceId:{\$workspaceId})")
7135
fun findByWorkspaceId(
7236
@Sanitize @Param("organizationId") organizationId: String,
@@ -81,4 +45,12 @@ interface RunnerRepository : RedisDocumentRepository<Runner, String> {
8145
@SecurityConstraint @Param("securityConstraint") securityConstraint: String,
8246
pageable: Pageable
8347
): Page<Runner>
48+
49+
@Query(
50+
"@organizationId:{\$organizationId} @workspaceId:{\$workspaceId} @datasets_parameter:{\$datasetId}")
51+
fun findByOrganizationIdAndWorkspaceIdAndDatasetsParameterValue(
52+
@Sanitize @Param("organizationId") organizationId: String,
53+
@Sanitize @Param("workspaceId") workspaceId: String,
54+
@Sanitize @Param("datasetId") datasetId: String
55+
): Optional<Runner>
8456
}

runner/src/main/kotlin/com/cosmotech/runner/service/RunnerApiServiceImpl.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package com.cosmotech.runner.service
44

55
import com.cosmotech.common.config.CsmPlatformProperties
6+
import com.cosmotech.common.events.GetAttachedRunnerToDataset
67
import com.cosmotech.common.events.RunDeleted
78
import com.cosmotech.common.rbac.PERMISSION_CREATE_CHILDREN
89
import com.cosmotech.common.rbac.PERMISSION_DELETE
@@ -269,4 +270,17 @@ internal class RunnerApiServiceImpl(
269270
runnerService.getInstance(runDeleted.runnerId)
270271
}
271272
}
273+
274+
@EventListener(GetAttachedRunnerToDataset::class)
275+
fun onGetAttachedRunnerToDataset(getAttachedRunnerToDataset: GetAttachedRunnerToDataset) {
276+
val organizationId = getAttachedRunnerToDataset.organizationId
277+
val workspaceId = getAttachedRunnerToDataset.workspaceId
278+
val datasetId = getAttachedRunnerToDataset.datasetId
279+
val runnerService = getRunnerService().inOrganization(organizationId).inWorkspace(workspaceId)
280+
281+
val runnerId =
282+
runnerService.findRunnerByDatasetParameter(organizationId, workspaceId, datasetId)?.id
283+
284+
getAttachedRunnerToDataset.response = runnerId
285+
}
272286
}

runner/src/main/kotlin/com/cosmotech/runner/service/RunnerService.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import com.cosmotech.workspace.domain.Workspace
5454
import com.cosmotech.workspace.service.toGenericSecurity
5555
import java.time.Instant
5656
import kotlin.collections.mutableListOf
57+
import kotlin.jvm.optionals.getOrNull
5758
import org.springframework.context.annotation.Scope
5859
import org.springframework.data.domain.PageRequest
5960
import org.springframework.data.domain.Pageable
@@ -175,6 +176,17 @@ class RunnerService(
175176
}
176177
}
177178

179+
fun findRunnerByDatasetParameter(
180+
organizationId: String,
181+
workspaceId: String,
182+
datasetId: String
183+
): Runner? {
184+
return runnerRepository
185+
.findByOrganizationIdAndWorkspaceIdAndDatasetsParameterValue(
186+
organizationId, workspaceId, datasetId)
187+
.getOrNull()
188+
}
189+
178190
fun saveInstance(runnerInstance: RunnerInstance): Runner {
179191
return runnerRepository.save(runnerInstance.getRunnerDataObjet())
180192
}

runner/src/main/openapi/runner.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,8 +619,10 @@ components:
619619
description: Free form additional data
620620
additionalProperties: true
621621
datasets:
622+
x-field-extra-annotation: "@com.redis.om.spring.annotations.Indexed"
622623
description: definition of datasets used by the runner
623-
$ref: "#/components/schemas/RunnerDatasets"
624+
allOf:
625+
- $ref: "#/components/schemas/RunnerDatasets"
624626
runSizing:
625627
description: definition of resources needed for the runner run
626628
$ref: "#/components/schemas/RunnerResourceSizing"
@@ -907,6 +909,7 @@ components:
907909
items:
908910
type: string
909911
parameter:
912+
x-field-extra-annotation: "@com.redis.om.spring.annotations.Indexed"
910913
description: The dataset id used for dataset parameters on current Runner
911914
type: string
912915
parameters:

0 commit comments

Comments
 (0)