diff --git a/src/main/kotlin/org/cloud_assess/controller/ComputeResourcesController.kt b/src/main/kotlin/org/cloud_assess/controller/ComputeResourcesController.kt new file mode 100644 index 0000000..8644945 --- /dev/null +++ b/src/main/kotlin/org/cloud_assess/controller/ComputeResourcesController.kt @@ -0,0 +1,21 @@ +package org.cloud_assess.controller + +import org.cloud_assess.api.ComputeResourcesApi +import org.cloud_assess.dto.ComputeResourceListAssessmentDto +import org.cloud_assess.dto.ComputeResourceListDto +import org.cloud_assess.service.ComputeResourceService +import org.cloud_assess.service.MapperService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RestController + +@RestController +class ComputeResourcesController( + private val computeResourceService: ComputeResourceService, + private val mapperService: MapperService, +) : ComputeResourcesApi { + override fun assessComputeResources(computeResourceListDto: ComputeResourceListDto): ResponseEntity { + val analysis = computeResourceService.analyze(computeResourceListDto) + val dto = mapperService.map(analysis, computeResourceListDto) + return ResponseEntity.ok(dto) + } +} diff --git a/src/main/kotlin/org/cloud_assess/controller/StorageResourcesController.kt b/src/main/kotlin/org/cloud_assess/controller/StorageResourcesController.kt new file mode 100644 index 0000000..185cb55 --- /dev/null +++ b/src/main/kotlin/org/cloud_assess/controller/StorageResourcesController.kt @@ -0,0 +1,21 @@ +package org.cloud_assess.controller + +import org.cloud_assess.api.StorageResourcesApi +import org.cloud_assess.dto.StorageResourceListAssessmentDto +import org.cloud_assess.dto.StorageResourceListDto +import org.cloud_assess.service.MapperService +import org.cloud_assess.service.StorageResourceService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RestController + +@RestController +class StorageResourcesController( + private val storageResourceService: StorageResourceService, + private val mapperService: MapperService, +) : StorageResourcesApi { + override fun assessStorageResources(storageResourceListDto: StorageResourceListDto): ResponseEntity { + val analysis = storageResourceService.analyze(storageResourceListDto) + val dto = mapperService.map(analysis, storageResourceListDto) + return ResponseEntity.ok(dto) + } +} diff --git a/src/main/kotlin/org/cloud_assess/service/AnalysisJobRunner.kt b/src/main/kotlin/org/cloud_assess/service/AnalysisJobRunner.kt new file mode 100644 index 0000000..c827465 --- /dev/null +++ b/src/main/kotlin/org/cloud_assess/service/AnalysisJobRunner.kt @@ -0,0 +1,40 @@ +package org.cloud_assess.service + +import ch.kleis.lcaac.core.assessment.ContributionAnalysisProgram +import ch.kleis.lcaac.core.lang.evaluator.Evaluator +import ch.kleis.lcaac.core.lang.expression.EProcessTemplateApplication +import ch.kleis.lcaac.core.math.basic.BasicNumber +import org.cloud_assess.dto.QuantityTimeDto +import org.cloud_assess.model.ProductMatcher +import org.cloud_assess.model.ResourceAnalysis + +class AnalysisJobRunner( + private val jobSize: Int, + private val productMatcher: (String) -> ProductMatcher, + private val periodDto: QuantityTimeDto, + private val evaluator: Evaluator, + + ) { + fun run(cases: Map>): Map { + return cases.entries + .chunked(jobSize) + .parallelStream() + .map { job -> + job.map { + val trace = evaluator.with(it.value.template).trace(it.value.template, it.value.arguments) + val systemValue = trace.getSystemValue() + val entryPoint = trace.getEntryPoint() + val program = ContributionAnalysisProgram(systemValue, entryPoint) + val rawAnalysis = program.run() + mapOf( + it.key to ResourceAnalysis( + productMatcher(it.key), + periodDto, + rawAnalysis + ) + ) + }.fold(emptyMap()) { acc, element -> acc.plus(element) } + }.reduce { acc, element -> acc.plus(element) } + .orElse(emptyMap()) + } +} diff --git a/src/main/kotlin/org/cloud_assess/service/ComputeResourceService.kt b/src/main/kotlin/org/cloud_assess/service/ComputeResourceService.kt new file mode 100644 index 0000000..ea2ce0f --- /dev/null +++ b/src/main/kotlin/org/cloud_assess/service/ComputeResourceService.kt @@ -0,0 +1,111 @@ +package org.cloud_assess.service + +import ch.kleis.lcaac.core.datasource.DefaultDataSourceOperations +import ch.kleis.lcaac.core.datasource.in_memory.InMemoryConnector +import ch.kleis.lcaac.core.datasource.in_memory.InMemoryConnectorKeys +import ch.kleis.lcaac.core.datasource.in_memory.InMemoryDatasource +import ch.kleis.lcaac.core.lang.SymbolTable +import ch.kleis.lcaac.core.lang.evaluator.Evaluator +import ch.kleis.lcaac.core.lang.expression.EProcessTemplateApplication +import ch.kleis.lcaac.core.lang.register.DataKey +import ch.kleis.lcaac.core.lang.value.RecordValue +import ch.kleis.lcaac.core.math.basic.BasicNumber +import ch.kleis.lcaac.core.math.basic.BasicOperations +import org.cloud_assess.dto.ComputeResourceListDto +import org.cloud_assess.model.ProductMatcher +import org.cloud_assess.model.ResourceAnalysis +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service + +@Service +class ComputeResourceService( + @Value("\${COMPUTE_JOB_SIZE:100}") + private val jobSize: Int, + private val parsingService: ParsingService, + private val defaultDataSourceOperations: DefaultDataSourceOperations, + private val symbolTable: SymbolTable, +) { + private val overrideTimeWindowParam = "timewindow" + private val overriddenDataSourceName = "compute_inventory" + private val helper = Helper( + defaultDataSourceOperations, + symbolTable, + ) + + + fun analyze(computeResources: ComputeResourceListDto): Map { + val period = with(helper) { + computeResources.period.toDataExpression() + } + val cases = cases(computeResources) + val computeResourcesConnector = inMemoryConnector(computeResources) + val sourceOps = defaultDataSourceOperations.overrideWith(computeResourcesConnector) + val evaluator = Evaluator( + symbolTable.copy( + data = symbolTable.data.override( + DataKey(overrideTimeWindowParam), + period, + ) + ), + BasicOperations, + sourceOps, + ) + val jobRunner = AnalysisJobRunner( + jobSize = jobSize, + productMatcher = { id -> + ProductMatcher( + name = "compute", + process = "compute_fn", + arguments = mapOf("id" to id) + ) + }, + periodDto = computeResources.period, + evaluator = evaluator, + ) + val analysis = jobRunner.run(cases) + return analysis + } + + private fun inMemoryConnector( + computeResources: ComputeResourceListDto, + ): InMemoryConnector { + val records = with(helper) { + computeResources.computeResources + .map { computeResource -> + RecordValue( + mapOf( + "id" to localEval(computeResource.id.toDataExpression()), + "pool_id" to localEval(computeResource.poolId.toDataExpression()), + "vcpu_size" to localEval(computeResource.vcpu.toDataExpression()), + "quantity" to localEval(computeResource.quantity.toDataExpression()), + ) + ) + } + } + val content = mapOf( + overriddenDataSourceName to InMemoryDatasource(records) + ) + return InMemoryConnector( + config = InMemoryConnectorKeys.defaultConfig(cacheEnabled = true, cacheSize = 1024), + content = content, + ) + } + + private fun cases(computeResources: ComputeResourceListDto): Map> { + val period = with(helper) { computeResources.period.toLcaac() } + val cases = computeResources.computeResources.associate { + val content = """ + process __main__ { + products { + 1 u __main__ + } + inputs { + $period compute from compute_fn(id = "${it.id}") + } + } + """.trimIndent() + it.id to parsingService.processTemplateApplication(content) + } + return cases + } +} diff --git a/src/main/kotlin/org/cloud_assess/service/Helper.kt b/src/main/kotlin/org/cloud_assess/service/Helper.kt new file mode 100644 index 0000000..eb04323 --- /dev/null +++ b/src/main/kotlin/org/cloud_assess/service/Helper.kt @@ -0,0 +1,67 @@ +package org.cloud_assess.service + +import ch.kleis.lcaac.core.datasource.DefaultDataSourceOperations +import ch.kleis.lcaac.core.lang.SymbolTable +import ch.kleis.lcaac.core.lang.evaluator.ToValue +import ch.kleis.lcaac.core.lang.evaluator.reducer.DataExpressionReducer +import ch.kleis.lcaac.core.lang.expression.DataExpression +import ch.kleis.lcaac.core.lang.expression.EDataRef +import ch.kleis.lcaac.core.lang.expression.EQuantityScale +import ch.kleis.lcaac.core.lang.expression.EStringLiteral +import ch.kleis.lcaac.core.lang.register.DataSourceRegister +import ch.kleis.lcaac.core.lang.value.DataValue +import ch.kleis.lcaac.core.math.basic.BasicNumber +import ch.kleis.lcaac.core.math.basic.BasicOperations +import org.cloud_assess.dto.* + +class Helper( + defaultDataSourceOperations: DefaultDataSourceOperations, + symbolTable: SymbolTable, +) { + private val dataReducer = DataExpressionReducer( + dataRegister = symbolTable.data, + dataSourceRegister = DataSourceRegister.empty(), + ops = BasicOperations, + sourceOps = defaultDataSourceOperations, + ) + + fun localEval(expression: DataExpression): DataValue { + val data = dataReducer.reduce(expression) + return with(ToValue(BasicOperations)) { + data.toValue() + } + } + + fun QuantityTimeDto.toDataExpression(): DataExpression { + return when (this.unit) { + TimeUnitsDto.hour -> EQuantityScale(BasicNumber(this.amount), EDataRef("hour")) + } + } + + fun QuantityTimeDto.toLcaac(): String { + return when (this.unit) { + TimeUnitsDto.hour -> "${this.amount} hour" + } + } + + fun String.toDataExpression(): DataExpression = EStringLiteral(this) + + fun QuantityMemoryDto.toDataExpression(): DataExpression { + return when (this.unit) { + MemoryUnitsDto.gB -> EQuantityScale(BasicNumber(this.amount), EDataRef("GB")) + MemoryUnitsDto.tB -> EQuantityScale(BasicNumber(this.amount), EDataRef("TB")) + } + } + + fun QuantityVCPUDto.toDataExpression(): DataExpression { + return when (this.unit) { + VCPUUnitsDto.vCPU -> EQuantityScale(BasicNumber(this.amount), EDataRef("vCPU")) + } + } + + fun QuantityDimensionlessDto.toDataExpression(): DataExpression { + return when (this.unit) { + DimensionlessUnitsDto.u -> EQuantityScale(BasicNumber(this.amount), EDataRef("u")) + } + } +} diff --git a/src/main/kotlin/org/cloud_assess/service/MapperService.kt b/src/main/kotlin/org/cloud_assess/service/MapperService.kt index f766ad7..cdb38f7 100644 --- a/src/main/kotlin/org/cloud_assess/service/MapperService.kt +++ b/src/main/kotlin/org/cloud_assess/service/MapperService.kt @@ -105,6 +105,22 @@ class MapperService { }.toList() } + fun map( + analysis: Map, + dto: ComputeResourceListDto, + ): ComputeResourceListAssessmentDto { + return ComputeResourceListAssessmentDto( + dto.computeResources.map { + val computeResourceAnalysis = analysis[it.id] ?: throw IllegalStateException("Unknown compute_resource '${it.id}'") + ComputeResourceAssessmentDto( + period = dto.period, + request = it, + impacts = impactsDto(computeResourceAnalysis) + ) + } + ) + } + fun map( analysis: Map, dto: PoolListDto @@ -182,4 +198,17 @@ class MapperService { IR = impactDto(analysis, Indicator.IR), ) } + + fun map(analysis: Map, dto: StorageResourceListDto): StorageResourceListAssessmentDto { + return StorageResourceListAssessmentDto( + dto.storageResources.map { + val storageResourceAnalysis = analysis[it.id] ?: throw IllegalStateException("Unknown storage_resource '${it.id}'") + StorageResourceAssessmentDto( + period = dto.period, + request = it, + impacts = impactsDto(storageResourceAnalysis), + ) + } + ) + } } diff --git a/src/main/kotlin/org/cloud_assess/service/PoolService.kt b/src/main/kotlin/org/cloud_assess/service/PoolService.kt index 3016b04..745e07c 100644 --- a/src/main/kotlin/org/cloud_assess/service/PoolService.kt +++ b/src/main/kotlin/org/cloud_assess/service/PoolService.kt @@ -8,7 +8,6 @@ import ch.kleis.lcaac.core.lang.expression.EProcessTemplateApplication import ch.kleis.lcaac.core.math.basic.BasicNumber import ch.kleis.lcaac.core.math.basic.BasicOperations import org.cloud_assess.dto.PoolListDto -import org.cloud_assess.dto.TimeUnitsDto import org.cloud_assess.model.ProductMatcher import org.cloud_assess.model.ResourceAnalysis import org.springframework.stereotype.Service @@ -19,6 +18,10 @@ class PoolService( private val defaultDataSourceOperations: DefaultDataSourceOperations, private val symbolTable: SymbolTable, ) { + private val helper = Helper( + defaultDataSourceOperations, + symbolTable, + ) @Suppress("DuplicatedCode") fun analyze(pools: PoolListDto): Map { @@ -50,9 +53,7 @@ class PoolService( private fun cases( pools: PoolListDto, ): Map> { - val period = when (pools.period.unit) { - TimeUnitsDto.hour -> "${pools.period.amount} hour" - } + val period = with(helper) { pools.period.toLcaac() } val cases = pools.pools.associate { val content = """ process __main__ { diff --git a/src/main/kotlin/org/cloud_assess/service/StorageResourceService.kt b/src/main/kotlin/org/cloud_assess/service/StorageResourceService.kt new file mode 100644 index 0000000..62fd6bf --- /dev/null +++ b/src/main/kotlin/org/cloud_assess/service/StorageResourceService.kt @@ -0,0 +1,108 @@ +package org.cloud_assess.service + +import ch.kleis.lcaac.core.datasource.DefaultDataSourceOperations +import ch.kleis.lcaac.core.datasource.in_memory.InMemoryConnector +import ch.kleis.lcaac.core.datasource.in_memory.InMemoryConnectorKeys +import ch.kleis.lcaac.core.datasource.in_memory.InMemoryDatasource +import ch.kleis.lcaac.core.lang.SymbolTable +import ch.kleis.lcaac.core.lang.evaluator.Evaluator +import ch.kleis.lcaac.core.lang.expression.EProcessTemplateApplication +import ch.kleis.lcaac.core.lang.register.DataKey +import ch.kleis.lcaac.core.lang.value.RecordValue +import ch.kleis.lcaac.core.math.basic.BasicNumber +import ch.kleis.lcaac.core.math.basic.BasicOperations +import org.cloud_assess.dto.StorageResourceListDto +import org.cloud_assess.model.ProductMatcher +import org.cloud_assess.model.ResourceAnalysis +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service + +@Service +class StorageResourceService( + @Value("\${COMPUTE_JOB_SIZE:100}") + private val jobSize: Int, + private val parsingService: ParsingService, + private val defaultDataSourceOperations: DefaultDataSourceOperations, + private val symbolTable: SymbolTable, +) { + private val overrideTimeWindowParam = "timewindow" + private val overriddenDataSourceName = "storage_space_inventory" + private val helper = Helper( + defaultDataSourceOperations, + symbolTable, + ) + fun analyze(storageResources: StorageResourceListDto): Map { + val period = with(helper) { + storageResources.period.toDataExpression() + } + val cases = cases(storageResources) + val storageResourcesConnector = inMemoryConnector(storageResources) + val sourceOps = defaultDataSourceOperations.overrideWith(storageResourcesConnector) + val evaluator = Evaluator( + symbolTable.copy( + data = symbolTable.data.override( + DataKey(overrideTimeWindowParam), + period, + ) + ), + BasicOperations, + sourceOps, + ) + val jobRunner = AnalysisJobRunner( + jobSize = jobSize, + productMatcher = { id -> + ProductMatcher( + name = "storage", + process = "storage_space_fn", + arguments = mapOf("id" to id) + ) + }, + periodDto = storageResources.period, + evaluator = evaluator, + ) + val analysis = jobRunner.run(cases) + return analysis + } + + private fun inMemoryConnector(storageResources: StorageResourceListDto): InMemoryConnector { + val records = with(helper) { + storageResources.storageResources + .map { storageResource -> + RecordValue( + mapOf( + "id" to localEval(storageResource.id.toDataExpression()), + "pool_id" to localEval(storageResource.poolId.toDataExpression()), + "vcpu_size" to localEval(storageResource.vcpu.toDataExpression()), + "volume" to localEval(storageResource.storage.toDataExpression()), + "quantity" to localEval(storageResource.quantity.toDataExpression()), + ) + ) + } + } + val content = mapOf( + overriddenDataSourceName to InMemoryDatasource(records) + ) + return InMemoryConnector( + config = InMemoryConnectorKeys.defaultConfig(cacheEnabled = true, cacheSize = 1024), + content = content, + ) + } + + private fun cases(storageResources: StorageResourceListDto): Map> { + val period = with(helper) { storageResources.period.toLcaac() } + val cases = storageResources.storageResources.associate { + val content = """ + process __main__ { + products { + 1 u __main__ + } + inputs { + $period storage from storage_space_fn(id = "${it.id}") + } + } + """.trimIndent() + it.id to parsingService.processTemplateApplication(content) + } + return cases + } +} diff --git a/src/main/kotlin/org/cloud_assess/service/VirtualMachineService.kt b/src/main/kotlin/org/cloud_assess/service/VirtualMachineService.kt index b9d569d..68e483d 100644 --- a/src/main/kotlin/org/cloud_assess/service/VirtualMachineService.kt +++ b/src/main/kotlin/org/cloud_assess/service/VirtualMachineService.kt @@ -1,22 +1,17 @@ package org.cloud_assess.service -import ch.kleis.lcaac.core.assessment.ContributionAnalysisProgram import ch.kleis.lcaac.core.datasource.DefaultDataSourceOperations import ch.kleis.lcaac.core.datasource.in_memory.InMemoryConnector import ch.kleis.lcaac.core.datasource.in_memory.InMemoryConnectorKeys import ch.kleis.lcaac.core.datasource.in_memory.InMemoryDatasource import ch.kleis.lcaac.core.lang.SymbolTable import ch.kleis.lcaac.core.lang.evaluator.Evaluator -import ch.kleis.lcaac.core.lang.evaluator.ToValue -import ch.kleis.lcaac.core.lang.evaluator.reducer.DataExpressionReducer -import ch.kleis.lcaac.core.lang.expression.* +import ch.kleis.lcaac.core.lang.expression.EProcessTemplateApplication import ch.kleis.lcaac.core.lang.register.DataKey -import ch.kleis.lcaac.core.lang.register.DataSourceRegister -import ch.kleis.lcaac.core.lang.value.DataValue import ch.kleis.lcaac.core.lang.value.RecordValue import ch.kleis.lcaac.core.math.basic.BasicNumber import ch.kleis.lcaac.core.math.basic.BasicOperations -import org.cloud_assess.dto.* +import org.cloud_assess.dto.VirtualMachineListDto import org.cloud_assess.model.ProductMatcher import org.cloud_assess.model.ResourceAnalysis import org.springframework.beans.factory.annotation.Value @@ -30,69 +25,50 @@ class VirtualMachineService( private val defaultDataSourceOperations: DefaultDataSourceOperations, private val symbolTable: SymbolTable, ) { - private val overrideTimeWindowParam = "vm_timewindow" + private val overrideTimeWindowParam = "timewindow" private val overriddenDataSourceName = "vm_inventory" - private val dataReducer = DataExpressionReducer( - dataRegister = symbolTable.data, - dataSourceRegister = DataSourceRegister.empty(), - ops = BasicOperations, - sourceOps = defaultDataSourceOperations, + private val helper = Helper( + defaultDataSourceOperations, + symbolTable, ) - private fun localEval(expression: DataExpression): DataValue { - val data = dataReducer.reduce(expression) - return with(ToValue(BasicOperations)) { - data.toValue() - } - } - - @Suppress("DuplicatedCode") fun analyze(vms: VirtualMachineListDto): Map { - val period = vms.period + val period = with(helper) { + vms.period.toDataExpression() + } val cases = cases(vms) val vmsConnector = inMemoryConnector(vms) - val analysis = cases.entries - .chunked(jobSize) - .parallelStream() - .map { job -> - job.map { - val sourceOps = defaultDataSourceOperations.overrideWith(vmsConnector) - val evaluator = Evaluator( - symbolTable.copy( - data = symbolTable.data.override( - DataKey(overrideTimeWindowParam), - period.toDataExpression(), - ) - ), - BasicOperations, - sourceOps, - ) - val trace = evaluator.with(it.value.template).trace(it.value.template, it.value.arguments) - val systemValue = trace.getSystemValue() - val entryPoint = trace.getEntryPoint() - val program = ContributionAnalysisProgram(systemValue, entryPoint) - val rawAnalysis = program.run() - mapOf( - it.key to ResourceAnalysis( - ProductMatcher( - name = "vm", - process = "vm_fn", - arguments = mapOf("id" to it.key) - ), period, rawAnalysis - ) - ) - }.fold(emptyMap()) { acc, element -> acc.plus(element) } - }.reduce { acc, element -> acc.plus(element) } - .orElse(emptyMap()) + val sourceOps = defaultDataSourceOperations.overrideWith(vmsConnector) + val evaluator = Evaluator( + symbolTable.copy( + data = symbolTable.data.override( + DataKey(overrideTimeWindowParam), + period, + ) + ), + BasicOperations, + sourceOps, + ) + val jobRunner = AnalysisJobRunner( + jobSize = jobSize, + productMatcher = { id -> + ProductMatcher( + name = "vm", + process = "vm_fn", + arguments = mapOf("id" to id) + ) + }, + periodDto = vms.period, + evaluator = evaluator, + ) + val analysis = jobRunner.run(cases) return analysis } private fun cases( vms: VirtualMachineListDto, ): Map> { - val period = when (vms.period.unit) { - TimeUnitsDto.hour -> "${vms.period.amount} hour" - } + val period = with(helper) { vms.period.toLcaac() } val cases = vms.virtualMachines.associate { val content = """ process __main__ { @@ -112,19 +88,21 @@ class VirtualMachineService( private fun inMemoryConnector( vms: VirtualMachineListDto, ): InMemoryConnector { - val records = vms.virtualMachines - .map { vm -> - RecordValue( - mapOf( - "id" to localEval(vm.id.toDataExpression()), - "pool_id" to localEval(vm.poolId.toDataExpression()), - "ram_size" to localEval(vm.ram.toDataExpression()), - "storage_size" to localEval(vm.storage.toDataExpression()), - "vcpu_size" to localEval(vm.vcpu.toDataExpression()), - "quantity" to localEval(vm.quantity.toDataExpression()), + val records = with(helper) { + vms.virtualMachines + .map { vm -> + RecordValue( + mapOf( + "id" to localEval(vm.id.toDataExpression()), + "pool_id" to localEval(vm.poolId.toDataExpression()), + "ram_size" to localEval(vm.ram.toDataExpression()), + "storage_size" to localEval(vm.storage.toDataExpression()), + "vcpu_size" to localEval(vm.vcpu.toDataExpression()), + "quantity" to localEval(vm.quantity.toDataExpression()), + ) ) - ) - } + } + } val content = mapOf( overriddenDataSourceName to InMemoryDatasource(records) ) @@ -133,31 +111,4 @@ class VirtualMachineService( content = content, ) } - - private fun QuantityTimeDto.toDataExpression(): DataExpression { - return when (this.unit) { - TimeUnitsDto.hour -> EQuantityScale(BasicNumber(this.amount), EDataRef("hour")) - } - } - - private fun String.toDataExpression(): DataExpression = EStringLiteral(this) - - private fun QuantityMemoryDto.toDataExpression(): DataExpression { - return when (this.unit) { - MemoryUnitsDto.gB -> EQuantityScale(BasicNumber(this.amount), EDataRef("GB")) - MemoryUnitsDto.tB -> EQuantityScale(BasicNumber(this.amount), EDataRef("TB")) - } - } - - private fun QuantityVCPUDto.toDataExpression(): DataExpression { - return when (this.unit) { - VCPUUnitsDto.vCPU -> EQuantityScale(BasicNumber(this.amount), EDataRef("vCPU")) - } - } - - private fun QuantityDimensionlessDto.toDataExpression(): DataExpression { - return when (this.unit) { - DimensionlessUnitsDto.u -> EQuantityScale(BasicNumber(this.amount), EDataRef("u")) - } - } } diff --git a/src/test/kotlin/org/cloud_assess/CloudAssessApplicationTest.kt b/src/test/kotlin/org/cloud_assess/CloudAssessApplicationTest.kt index e30eebe..802f934 100644 --- a/src/test/kotlin/org/cloud_assess/CloudAssessApplicationTest.kt +++ b/src/test/kotlin/org/cloud_assess/CloudAssessApplicationTest.kt @@ -7,13 +7,12 @@ import ch.kleis.lcaac.core.lang.value.UnitValue import ch.kleis.lcaac.core.math.basic.BasicNumber import org.assertj.core.api.Assertions.assertThat import org.assertj.core.data.Percentage -import org.cloud_assess.dto.PoolListDto -import org.cloud_assess.dto.QuantityTimeDto -import org.cloud_assess.dto.TimeUnitsDto -import org.cloud_assess.dto.VirtualMachineListDto +import org.cloud_assess.dto.* import org.cloud_assess.fixtures.DtoFixture import org.cloud_assess.model.Indicator +import org.cloud_assess.service.ComputeResourceService import org.cloud_assess.service.PoolService +import org.cloud_assess.service.StorageResourceService import org.cloud_assess.service.VirtualMachineService import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired @@ -21,6 +20,10 @@ import org.springframework.boot.test.context.SpringBootTest @SpringBootTest class CloudAssessApplicationTest( + @Autowired + private val computeResourceService: ComputeResourceService, + @Autowired + private val storageResourceService: StorageResourceService, @Autowired private val virtualMachineService: VirtualMachineService, @Autowired @@ -124,4 +127,88 @@ class CloudAssessApplicationTest( ) } } + + @Test + fun computeResourceService() { + // given + val computeResources = ComputeResourceListDto( + period = QuantityTimeDto(1.0, TimeUnitsDto.hour), + computeResources = listOf( + DtoFixture.computeResourceDto("comp-01", "client_compute"), + DtoFixture.computeResourceDto("comp-02", "client_compute"), + ), + ) + + // when + val actual = computeResourceService.analyze(computeResources) + + // then + listOf("comp-01", "comp-02").forEach { id -> + val computeResource = actual[id]!! + assertThat(computeResource.period).isEqualTo(QuantityTimeDto(1.0, TimeUnitsDto.hour)) + + val total = computeResource.total(Indicator.GWP) + val manufacturing = computeResource.manufacturing(Indicator.GWP) + val transport = computeResource.transport(Indicator.GWP) + val use = computeResource.use(Indicator.GWP) + val endOfLife = computeResource.endOfLife(Indicator.GWP) + + assertGWPKgCO2Eq(total, 3.72540688945756) + assertGWPKgCO2Eq(manufacturing, 0.953923318828) + assertGWPKgCO2Eq(transport, 0.945360209961) + assertGWPKgCO2Eq(use, 0.8805447145145) + assertGWPKgCO2Eq(endOfLife, 0.945578646152) + assertThat( + total.amount.value + ).isCloseTo( + manufacturing.amount.value + + transport.amount.value + + use.amount.value + + endOfLife.amount.value, + Percentage.withPercentage(1e-3) + ) + } + } + + @Test + fun storageResourceService() { + // given + val storageResources = StorageResourceListDto( + period = QuantityTimeDto(1.0, TimeUnitsDto.hour), + storageResources = listOf( + DtoFixture.storageResourceDto("sto-sp-01", "client_storage_space"), + DtoFixture.storageResourceDto("sto-sp-02", "client_storage_space"), + ), + ) + + // when + val actual = storageResourceService.analyze(storageResources) + + // then + listOf("sto-sp-01", "sto-sp-02").forEach { id -> + val storageResource = actual[id]!! + assertThat(storageResource.period).isEqualTo(QuantityTimeDto(1.0, TimeUnitsDto.hour)) + + val total = storageResource.total(Indicator.GWP) + val manufacturing = storageResource.manufacturing(Indicator.GWP) + val transport = storageResource.transport(Indicator.GWP) + val use = storageResource.use(Indicator.GWP) + val endOfLife = storageResource.endOfLife(Indicator.GWP) + + assertGWPKgCO2Eq(total, 3.7885393335789) + assertGWPKgCO2Eq(manufacturing, 0.953923318828) + assertGWPKgCO2Eq(transport, 0.945360209961) + assertGWPKgCO2Eq(use, 0.9436771586359) + assertGWPKgCO2Eq(endOfLife, 0.945578646152) + assertThat( + total.amount.value + ).isCloseTo( + manufacturing.amount.value + + transport.amount.value + + use.amount.value + + endOfLife.amount.value, + Percentage.withPercentage(1e-3) + ) + } + } } diff --git a/src/test/kotlin/org/cloud_assess/controller/ComputeResourcesControllerTest.kt b/src/test/kotlin/org/cloud_assess/controller/ComputeResourcesControllerTest.kt new file mode 100644 index 0000000..7e66fb8 --- /dev/null +++ b/src/test/kotlin/org/cloud_assess/controller/ComputeResourcesControllerTest.kt @@ -0,0 +1,87 @@ +package org.cloud_assess.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import io.mockk.every +import io.mockk.mockk +import org.cloud_assess.dto.ComputeResourceListAssessmentDto +import org.cloud_assess.dto.ComputeResourceListDto +import org.cloud_assess.fixtures.DtoFixture.Companion.computeResourceAssessmentDto +import org.cloud_assess.fixtures.DtoFixture.Companion.computeResourceListDto +import org.cloud_assess.service.ComputeResourceService +import org.cloud_assess.service.MapperService +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.http.MediaType +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +@ExtendWith(SpringExtension::class) +@WebMvcTest(controllers = [ComputeResourcesController::class]) +class ComputeResourcesControllerTest { + @TestConfiguration + class ControllerTestConfig { + @Bean + fun mapperService(): MapperService = mockk() + + @Bean + fun computeResourceService(): ComputeResourceService = mockk() + } + + @Autowired + private lateinit var mockMvc: MockMvc + + @Autowired + private lateinit var computeResourceService: ComputeResourceService + + @Autowired + private lateinit var mapperService: MapperService + + @Autowired + private lateinit var objectMapper: ObjectMapper + + @Test + fun computeResources_whenCorrectDto_then200() { + // given + val dto = computeResourceListDto() + val outputDto = ComputeResourceListAssessmentDto( + computeResources = listOf(computeResourceAssessmentDto()) + ) + + every { computeResourceService.analyze(any()) } returns mockk() + every { mapperService.map(any(), any()) } returns outputDto + + // when/then + mockMvc.perform( + post("/compute_resources/assess") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ).andExpect(status().isOk) + } + + @Test + fun computeResources_whenInvalidDto_then400() { + // given + val dto = """ + { + "compute_resources": [ + { + "id": 3.0 + } + ] + } + """.trimIndent() + + // when/then + mockMvc.perform( + post("/compute_resources/assess") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ).andExpect(status().isBadRequest) + } +} diff --git a/src/test/kotlin/org/cloud_assess/controller/StorageResourcesControllerTest.kt b/src/test/kotlin/org/cloud_assess/controller/StorageResourcesControllerTest.kt new file mode 100644 index 0000000..4537ec0 --- /dev/null +++ b/src/test/kotlin/org/cloud_assess/controller/StorageResourcesControllerTest.kt @@ -0,0 +1,87 @@ +package org.cloud_assess.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import io.mockk.every +import io.mockk.mockk +import org.cloud_assess.dto.StorageResourceListAssessmentDto +import org.cloud_assess.dto.StorageResourceListDto +import org.cloud_assess.fixtures.DtoFixture.Companion.storageResourceAssessmentDto +import org.cloud_assess.fixtures.DtoFixture.Companion.storageResourceListDto +import org.cloud_assess.service.StorageResourceService +import org.cloud_assess.service.MapperService +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.http.MediaType +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +@ExtendWith(SpringExtension::class) +@WebMvcTest(controllers = [StorageResourcesController::class]) +class StorageResourcesControllerTest { + @TestConfiguration + class ControllerTestConfig { + @Bean + fun mapperService(): MapperService = mockk() + + @Bean + fun storageResourceService(): StorageResourceService = mockk() + } + + @Autowired + private lateinit var mockMvc: MockMvc + + @Autowired + private lateinit var storageResourceService: StorageResourceService + + @Autowired + private lateinit var mapperService: MapperService + + @Autowired + private lateinit var objectMapper: ObjectMapper + + @Test + fun storageResources_whenCorrectDto_then200() { + // given + val dto = storageResourceListDto() + val outputDto = StorageResourceListAssessmentDto( + storageResources = listOf(storageResourceAssessmentDto()) + ) + + every { storageResourceService.analyze(any()) } returns mockk() + every { mapperService.map(any(), any()) } returns outputDto + + // when/then + mockMvc.perform( + post("/storage_resources/assess") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ).andExpect(status().isOk) + } + + @Test + fun storageResources_whenInvalidDto_then400() { + // given + val dto = """ + { + "storage_resources": [ + { + "id": 3.0 + } + ] + } + """.trimIndent() + + // when/then + mockMvc.perform( + post("/storage_resources/assess") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ).andExpect(status().isBadRequest) + } +} diff --git a/src/test/kotlin/org/cloud_assess/fixtures/DtoFixture.kt b/src/test/kotlin/org/cloud_assess/fixtures/DtoFixture.kt index 511bbe9..682622e 100644 --- a/src/test/kotlin/org/cloud_assess/fixtures/DtoFixture.kt +++ b/src/test/kotlin/org/cloud_assess/fixtures/DtoFixture.kt @@ -139,6 +139,80 @@ class DtoFixture { ) } + fun computeResourceListDto(): ComputeResourceListDto { + return ComputeResourceListDto( + period = quantityHour(1.0), + computeResources = listOf( + computeResourceDto("comp-01"), + ), + ) + } + + fun computeResourceDto( + id: String = "comp-01", + poolId: String = "client_vm", + vcpu: Double = 1.0, + ): ComputeResourceDto { + return ComputeResourceDto( + id = id, + poolId = poolId, + quantity = quantityDimensionless(1.0), + vcpu = quantityVCPU(vcpu), + meta = mapOf( + "region" to "FR" + ) + ) + } + + fun computeResourceAssessmentDto( + id: String = "comp-01", + ): ComputeResourceAssessmentDto { + return ComputeResourceAssessmentDto( + period = quantityHour(1.0), + request = computeResourceDto(id), + impacts = impactsDto(), + ) + } + + + fun storageResourceListDto(): StorageResourceListDto { + return StorageResourceListDto( + period = quantityHour(1.0), + storageResources = listOf( + storageResourceDto("sto-sp-01") + ) + ) + } + + fun storageResourceDto( + id: String = "sto-sp-01", + poolId: String = "client_vm", + storage: Double = 10.0, + vcpu: Double = 1.0, + ): StorageResourceDto { + return StorageResourceDto( + id = id, + poolId = poolId, + storage = quantityMemory(storage), + vcpu = quantityVCPU(vcpu), + quantity = quantityDimensionless(1.0), + meta = mapOf( + "region" to "FR" + ) + ) + } + + fun storageResourceAssessmentDto( + id: String = "sto-sp-01", + ): StorageResourceAssessmentDto { + return StorageResourceAssessmentDto( + period = quantityHour(1.0), + request = storageResourceDto(id), + impacts = impactsDto(), + ) + } + + private fun quantityVCPU(amount: Double = 1.0): QuantityVCPUDto { return QuantityVCPUDto(amount, VCPUUnitsDto.vCPU) }