Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
9 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ enum class ErrorCode(
AUTH_CODE_NOT_FOUND("์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์ „์†ก ํ•ด์ฃผ์„ธ์š”.", 404),
DOMAIN_NOT_FOUND("ํ•ด๋‹น ๋„๋ฉ”์ธ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ", 404),
VOLUME_NOT_FOUND("ํ•ด๋‹น ๋ณผ๋ฅจ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ", 404),
VOLUME_MOUNT_NOT_FOUND("ํ•ด๋‹น ๋ณผ๋ฅจ ๋งˆ์šดํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ", 404),

CONFLICT("ํ•ด๋‹น ์š”์ฒญ์€ ์„œ๋ฒ„์˜ ์ƒํƒœ์™€ ์ถฉ๋Œ๋ฉ๋‹ˆ๋‹ค.", 409),
CAN_NOT_DEPLOY_APPLICATION("์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ •์ง€์‹œํ‚จ ํ›„ ์‹คํ–‰ํ•ด์ฃผ์„ธ์š”.", 409),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.dcd.server.core.domain.volume.exception

import com.dcd.server.core.common.error.BasicException
import com.dcd.server.core.common.error.ErrorCode

class VolumeMountNotFoundException : BasicException(ErrorCode.VOLUME_MOUNT_NOT_FOUND)
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.dcd.server.core.domain.application.model.Application
import java.util.UUID

class VolumeMount(
val id: UUID,
val application: Application,
val volume: Volume,
val mountPath: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ interface CommandVolumePort {
fun delete(volume: Volume)

fun saveMount(volumeMount: VolumeMount)

fun deleteMount(volumeMount: VolumeMount)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ interface QueryVolumePort {
fun findAllMountByApplication(application: Application): List<VolumeMount>

fun findAllMountByVolume(volume: Volume): List<VolumeMount>

fun findMountByApplicationAndVolume(application: Application, volume: Volume): VolumeMount?
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.dcd.server.core.domain.application.event.DeployApplicationEvent
import com.dcd.server.core.domain.application.exception.ApplicationNotFoundException
import com.dcd.server.core.domain.application.spi.QueryApplicationPort
import com.dcd.server.core.domain.volume.dto.request.MountVolumeReqDto
import com.dcd.server.core.domain.volume.exception.AlreadyExistsVolumeMountException
import com.dcd.server.core.domain.volume.exception.VolumeNotFoundException
import com.dcd.server.core.domain.volume.model.VolumeMount
import com.dcd.server.core.domain.volume.spi.CommandVolumePort
Expand Down Expand Up @@ -37,8 +38,10 @@ class MountVolumeUseCase(
if (application.workspace != workspace)
throw ApplicationNotFoundException()

if (queryVolumePort.findMountByApplicationAndVolume(application, volume) != null)
throw AlreadyExistsVolumeMountException()

val volumeMount = VolumeMount(
id = UUID.randomUUID(),
application = application,
volume = volume,
mountPath = mountVolumeReqDto.mountPath,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.dcd.server.core.domain.volume.usecase

import com.dcd.server.core.common.annotation.UseCase
import com.dcd.server.core.common.data.WorkspaceInfo
import com.dcd.server.core.domain.application.event.DeployApplicationEvent
import com.dcd.server.core.domain.application.exception.ApplicationNotFoundException
import com.dcd.server.core.domain.application.spi.QueryApplicationPort
import com.dcd.server.core.domain.volume.exception.VolumeMountNotFoundException
import com.dcd.server.core.domain.volume.exception.VolumeNotFoundException
import com.dcd.server.core.domain.volume.spi.CommandVolumePort
import com.dcd.server.core.domain.volume.spi.QueryVolumePort
import com.dcd.server.core.domain.workspace.exception.WorkspaceNotFoundException
import org.springframework.context.ApplicationEventPublisher
import java.util.UUID

@UseCase
class UnMountVolumeUseCase(
private val queryVolumePort: QueryVolumePort,
private val queryApplicationPort: QueryApplicationPort,
private val commandVolumePort: CommandVolumePort,
private val workspaceInfo: WorkspaceInfo,
private val eventPublisher: ApplicationEventPublisher
) {
fun execute(volumeId: UUID, applicationId: String) {
val workspace = (workspaceInfo.workspace
?: throw WorkspaceNotFoundException())

val volume = (queryVolumePort.findById(volumeId)
?: throw VolumeNotFoundException())
if (volume.workspace != workspace)
throw VolumeNotFoundException()

val application = (queryApplicationPort.findById(applicationId)
?: throw ApplicationNotFoundException())
if (application.workspace != workspace)
throw ApplicationNotFoundException()

val volumeMount = queryVolumePort.findMountByApplicationAndVolume(application, volume)
?: throw VolumeMountNotFoundException()
commandVolumePort.deleteMount(volumeMount)

eventPublisher.publishEvent(DeployApplicationEvent(listOf(application.id)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class SecurityConfig(
it.requestMatchers(HttpMethod.GET, "/{workspaceId}/volume").authenticated()
it.requestMatchers(HttpMethod.GET, "/{workspaceId}/volume/{volumeId}").authenticated()
it.requestMatchers(HttpMethod.POST, "/{workspaceId}/volume/{volumeId}/mount").authenticated()
it.requestMatchers(HttpMethod.DELETE, "/{workspaceId}/volume/{volumeId}/mount").authenticated()

//when url not set
it.anyRequest().denyAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class VolumePersistenceAdapter(
volumeMountRepository.save(volumeMount.toEntity())
}

override fun deleteMount(volumeMount: VolumeMount) {
volumeMountRepository.delete(volumeMount.toEntity())
}

override fun findById(id: UUID): Volume? =
volumeRepository.findByIdOrNull(id)
?.toDomain()
Expand All @@ -53,4 +57,8 @@ class VolumePersistenceAdapter(
override fun findAllMountByVolume(volume: Volume): List<VolumeMount> =
volumeMountRepository.findAllByVolume(volume.toEntity())
.map { it.toDomain() }

override fun findMountByApplicationAndVolume(application: Application, volume: Volume): VolumeMount? =
volumeMountRepository.findByVolumeAndApplication(volume.toEntity(), application.toEntity())
?.toDomain()
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ fun VolumeJpaEntity.toDomain(): Volume =

fun VolumeMount.toEntity(): VolumeMountJpaEntity =
VolumeMountJpaEntity(
id = this.id,
application = this.application.toEntity(),
volume = this.volume.toEntity(),
mountPath = this.mountPath,
Expand All @@ -36,7 +35,6 @@ fun VolumeMount.toEntity(): VolumeMountJpaEntity =

fun VolumeMountJpaEntity.toDomain(): VolumeMount =
VolumeMount(
id = this.id,
application = this.application.toDomain(),
volume = this.volume.toDomain(),
mountPath = this.mountPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,38 @@ package com.dcd.server.persistence.volume.entity

import com.dcd.server.persistence.application.entity.ApplicationJpaEntity
import jakarta.persistence.Column
import jakarta.persistence.Embeddable
import jakarta.persistence.EmbeddedId
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import jakarta.persistence.MapsId
import jakarta.persistence.Table
import java.io.Serializable
import java.util.UUID

@Entity
@Table(name = "volume_mount_entity")
class VolumeMountJpaEntity(
@Id
@Column(columnDefinition = "BINARY(16)")
val id: UUID = UUID.randomUUID(),
@MapsId("applicationId")
@ManyToOne
@JoinColumn(name = "application_id")
val application: ApplicationJpaEntity,
@MapsId("volumeId")
@ManyToOne
@JoinColumn(name = "volume_id")
val volume: VolumeJpaEntity,
val mountPath: String,
val readOnly: Boolean
)
) {
@EmbeddedId
val id: VolumeMountId = VolumeMountId(application.id, volume.id)
@Embeddable
data class VolumeMountId(
@Column(nullable = false)
val applicationId: UUID,
@Column(nullable = false)
val volumeId: UUID
) : Serializable
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import com.dcd.server.persistence.volume.entity.VolumeMountJpaEntity
import org.springframework.data.jpa.repository.JpaRepository
import java.util.UUID

interface VolumeMountRepository : JpaRepository<VolumeMountJpaEntity, UUID> {
interface VolumeMountRepository : JpaRepository<VolumeMountJpaEntity, VolumeMountJpaEntity.VolumeMountId> {
fun findAllByVolume(volume: VolumeJpaEntity): List<VolumeMountJpaEntity>
fun findAllByApplication(application: ApplicationJpaEntity): List<VolumeMountJpaEntity>
fun findByVolumeAndApplication(volume: VolumeJpaEntity, application: ApplicationJpaEntity): VolumeMountJpaEntity?
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.dcd.server.core.domain.volume.usecase.DeleteVolumeUseCase
import com.dcd.server.core.domain.volume.usecase.GetAllVolumeUseCase
import com.dcd.server.core.domain.volume.usecase.GetOneVolumeUseCase
import com.dcd.server.core.domain.volume.usecase.MountVolumeUseCase
import com.dcd.server.core.domain.volume.usecase.UnMountVolumeUseCase
import com.dcd.server.core.domain.volume.usecase.UpdateVolumeUseCase
import com.dcd.server.presentation.common.annotation.WebAdapter
import com.dcd.server.presentation.domain.volume.data.extension.toDto
Expand Down Expand Up @@ -33,7 +34,8 @@ class VolumeWebAdapter(
private val updateVolumeUseCase: UpdateVolumeUseCase,
private val getAllVolumeUseCase: GetAllVolumeUseCase,
private val getOneVolumeUseCase: GetOneVolumeUseCase,
private val mountVolumeUseCase: MountVolumeUseCase
private val mountVolumeUseCase: MountVolumeUseCase,
private val unMountVolumeUseCase: UnMountVolumeUseCase
) {
@PostMapping
@WorkspaceOwnerVerification("#workspaceId")
Expand Down Expand Up @@ -88,4 +90,14 @@ class VolumeWebAdapter(
): ResponseEntity<Void> =
mountVolumeUseCase.execute(volumeId, applicationId, mountVolumeRequest.toDto())
.run { ResponseEntity.ok().build() }

@DeleteMapping("/{volumeId}/mount")
@WorkspaceOwnerVerification("#workspaceId")
fun unMountVolume(
@PathVariable workspaceId: String,
@PathVariable volumeId: UUID,
@RequestParam applicationId: String,
): ResponseEntity<Void> =
unMountVolumeUseCase.execute(volumeId, applicationId)
.run { ResponseEntity.ok().build() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ class DeleteVolumeUseCaseTest(
val application = queryApplicationPort.findById("2fb0f315-8272-422f-8e9f-c4f765c022b2")!!
val volume = volumeRepository.findByIdOrNull(targetVolumeId)!!.toDomain()
val volumeMount = VolumeMount(
id = UUID.randomUUID(),
application = application,
volume = volume,
mountPath = "/test/volume",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package com.dcd.server.core.domain.volume.usecase
import com.dcd.server.core.common.command.CommandPort
import com.dcd.server.core.common.data.WorkspaceInfo
import com.dcd.server.core.domain.application.exception.ApplicationNotFoundException
import com.dcd.server.core.domain.application.spi.QueryApplicationPort
import com.dcd.server.core.domain.volume.dto.request.MountVolumeReqDto
import com.dcd.server.core.domain.volume.exception.AlreadyExistsVolumeMountException
import com.dcd.server.core.domain.volume.exception.VolumeNotFoundException
import com.dcd.server.core.domain.volume.model.Volume
import com.dcd.server.core.domain.volume.model.VolumeMount
import com.dcd.server.core.domain.workspace.exception.WorkspaceNotFoundException
import com.dcd.server.core.domain.workspace.spi.QueryWorkspacePort
import com.dcd.server.persistence.volume.adapter.toDomain
import com.dcd.server.persistence.volume.adapter.toEntity
import com.dcd.server.persistence.volume.repository.VolumeMountRepository
import com.dcd.server.persistence.volume.repository.VolumeRepository
Expand All @@ -18,6 +22,7 @@ import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.data.repository.findByIdOrNull
import org.springframework.test.context.ActiveProfiles
import org.springframework.transaction.annotation.Transactional
import util.workspace.WorkspaceGenerator
Expand All @@ -31,6 +36,7 @@ class MountVolumeUseCaseTest(
@MockkBean(relaxed = true)
private val commandPort: CommandPort,
private val queryWorkspacePort: QueryWorkspacePort,
private val queryApplicationPort: QueryApplicationPort,
private val volumeRepository: VolumeRepository,
private val volumeMountRepository: VolumeMountRepository,
private val workspaceRepository: WorkspaceRepository,
Expand Down Expand Up @@ -107,6 +113,26 @@ class MountVolumeUseCaseTest(
}
}
}

`when`("์ด๋ฏธ ๋ณผ๋ฅจ์— ๋งˆ์šดํŠธ ๋์„๋•Œ") {
beforeContainer {
val application = queryApplicationPort.findById(targetApplicationId)!!
val volume = volumeRepository.findByIdOrNull(targetVolumeId)!!.toDomain()
val volumeMount = VolumeMount(
application = application,
volume = volume,
mountPath = "/test/volume",
readOnly = false
)
volumeMountRepository.save(volumeMount.toEntity())
}

then("์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด์•ผํ•จ") {
shouldThrow<AlreadyExistsVolumeMountException> {
mountVolumeUseCase.execute(targetVolumeId, targetApplicationId, request)
}
}
}
}

given("์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์€ ์›Œํฌ์ŠคํŽ˜์ด์Šค๊ฐ€ ์„ธํŒ…๋˜๊ณ ") {
Expand Down
Loading