From 1fed88a4711965649b81d46e3fdee634429db269 Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Fri, 2 May 2025 12:09:44 +0300 Subject: [PATCH 1/8] Created metrics endpoints for TMF Service related information --- .../tmf/metrics/api/ServiceMetricsApi.java | 47 ++++ .../api/ServiceMetricsApiController.java | 84 +++++++ .../ServiceMetricsRepoService.java | 45 ++++ .../tmf/sim638/repo/ServiceRepository.java | 14 ++ .../ServiceMetricsApiControllerTest.java | 215 ++++++++++++++++++ 5 files changed, 405 insertions(+) create mode 100644 src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java create mode 100644 src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java create mode 100644 src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java create mode 100644 src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java new file mode 100644 index 00000000..da1c1174 --- /dev/null +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java @@ -0,0 +1,47 @@ +package org.etsi.osl.tmf.metrics.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.etsi.osl.tmf.common.model.service.ServiceStateType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.time.OffsetDateTime; +import java.util.Map; + +@Tag(name = "ServiceMetricsApi") +public interface ServiceMetricsApi { + + Logger log = LoggerFactory.getLogger(ServiceMetricsApi.class); + + @Operation(summary = "Get total number of services", operationId = "getTotalServices") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "400", description = "Bad Request"), + @ApiResponse(responseCode = "500", description = "Internal Server Error") + }) + @RequestMapping(value = "/tmf-api/metrics/totalServices", method = RequestMethod.GET, produces = "application/json;charset=utf-8") + ResponseEntity> getTotalServices( + @Valid @RequestParam(value = "state", required = false) ServiceStateType state + ); + + @Operation(summary = "Get services grouped by state", operationId = "getServicesGroupedByState") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "400", description = "Bad Request"), + @ApiResponse(responseCode = "500", description = "Internal Server Error") + }) + @RequestMapping(value = "/tmf-api/metrics/servicesGroupByState", method = RequestMethod.GET, produces = "application/json;charset=utf-8") + ResponseEntity> getServicesGroupedByState( + @Valid @RequestParam(value = "starttime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime starttime, + @Valid @RequestParam(value = "endtime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime endtime + ); +} diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java new file mode 100644 index 00000000..1c0a8007 --- /dev/null +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java @@ -0,0 +1,84 @@ +package org.etsi.osl.tmf.metrics.api; + +import org.etsi.osl.tmf.common.model.service.ServiceStateType; +import org.etsi.osl.tmf.metrics.reposervices.ServiceMetricsRepoService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; + +import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Controller +public class ServiceMetricsApiController implements ServiceMetricsApi{ + + private static final Logger log = LoggerFactory.getLogger(ServiceMetricsApiController.class); + private final ServiceMetricsRepoService serviceMetricsRepoService; + + @Autowired + public ServiceMetricsApiController(ServiceMetricsRepoService serviceMetricsRepoService) { + this.serviceMetricsRepoService = serviceMetricsRepoService; + } + + @Override + public ResponseEntity> getTotalServices(ServiceStateType state) { + try { + int totalServices = serviceMetricsRepoService.countTotalServices(state); + Map response = new HashMap<>(); + response.put("totalServices", totalServices); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + log.error("Couldn't retrieve total services. ", e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @Override + public ResponseEntity> getServicesGroupedByState(OffsetDateTime starttime, OffsetDateTime endtime) { + try { + Map servicesByState = serviceMetricsRepoService.getServicesGroupedByState(starttime, endtime); + + // Initialize with all possible states and 0. Ensures that all states are represented, even if not present in the data. + Map fullStateMap = new LinkedHashMap<>(); + for (ServiceStateType state : ServiceStateType.values()) { + fullStateMap.put(state.name(), 0); // default to 0 + } + + // Overwrite counts with actual data + servicesByState.forEach((key, value) -> { + fullStateMap.put(key.toUpperCase(), value); // normalize case just in case + }); + + // Build groupByState list + List> groupByStateList = fullStateMap.entrySet().stream() + .map(entry -> { + Map map = new HashMap<>(); + map.put("key", entry.getKey()); + map.put("count", entry.getValue()); + return map; + }) + .toList(); + + + // Wrap in response structure + Map aggregations = Map.of("groupByState", groupByStateList); + Map services = Map.of( + "total", fullStateMap.values().stream().mapToInt(Integer::intValue).sum(), + "aggregations", aggregations + ); + + Map response = Map.of("services", services); + return new ResponseEntity<>(response, HttpStatus.OK); + + } catch (Exception e) { + log.error("Couldn't retrieve services grouped by state. ", e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java new file mode 100644 index 00000000..ef910dce --- /dev/null +++ b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java @@ -0,0 +1,45 @@ +package org.etsi.osl.tmf.metrics.reposervices; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.etsi.osl.tmf.common.model.service.ServiceStateType; +import org.etsi.osl.tmf.sim638.repo.ServiceRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +public class ServiceMetricsRepoService { + + @Autowired + ObjectMapper objectMapper; + + @Autowired + ServiceRepository serviceRepo; + + public int countTotalServices(ServiceStateType state) { + if (state == null) { + return serviceRepo.countAll(); + } else { + return serviceRepo.countByState(state); + } + } + + public Map getServicesGroupedByState(OffsetDateTime starttime, OffsetDateTime endtime) { + if (starttime.plusDays(31).isBefore(endtime)) { + starttime = endtime.minusDays(31); + } + + List rawResults = serviceRepo.groupByStateBetweenDates(starttime, endtime); + + return rawResults.stream() + .collect(Collectors.toMap( + row -> row[0].toString(), + row -> ((Number) row[1]).intValue() + )); + } + +} diff --git a/src/main/java/org/etsi/osl/tmf/sim638/repo/ServiceRepository.java b/src/main/java/org/etsi/osl/tmf/sim638/repo/ServiceRepository.java index 8c920375..ee3d9c3b 100644 --- a/src/main/java/org/etsi/osl/tmf/sim638/repo/ServiceRepository.java +++ b/src/main/java/org/etsi/osl/tmf/sim638/repo/ServiceRepository.java @@ -19,8 +19,11 @@ */ package org.etsi.osl.tmf.sim638.repo; +import java.time.OffsetDateTime; import java.util.List; import java.util.Optional; + +import org.etsi.osl.tmf.common.model.service.ServiceStateType; import org.etsi.osl.tmf.sim638.model.Service; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -68,4 +71,15 @@ public interface ServiceRepository extends JpaRepository { + "WHERE sres.id = ?1 " ) List findServicesHavingThisSupportingResourceID(String resourceID); + // Methods for metrics + + @Query("SELECT COUNT(srv) FROM Service srv") + int countAll(); + + int countByState(ServiceStateType state); + + @Query("SELECT srv.state, COUNT(srv) FROM Service srv " + + "WHERE srv.startDate >= :starttime AND srv.endDate <= :endtime " + + "GROUP BY srv.state") + List groupByStateBetweenDates(OffsetDateTime starttime, OffsetDateTime endtime); } diff --git a/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java new file mode 100644 index 00000000..2c0cc8d1 --- /dev/null +++ b/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java @@ -0,0 +1,215 @@ +package org.etsi.osl.services.api.metrics; + +import com.jayway.jsonpath.JsonPath; +import org.apache.commons.io.IOUtils; +import org.etsi.osl.tmf.JsonUtils; +import org.etsi.osl.tmf.OpenAPISpringBoot; +import org.etsi.osl.tmf.common.model.Any; +import org.etsi.osl.tmf.common.model.service.*; +import org.etsi.osl.tmf.scm633.model.ServiceSpecification; +import org.etsi.osl.tmf.scm633.model.ServiceSpecificationCreate; +import org.etsi.osl.tmf.sim638.model.Service; +import org.etsi.osl.tmf.sim638.model.ServiceCreate; +import org.etsi.osl.tmf.sim638.service.ServiceRepoService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@Transactional +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.MOCK, + classes = OpenAPISpringBoot.class +) +//@AutoConfigureTestDatabase //this automatically uses h2 +@AutoConfigureMockMvc +@ActiveProfiles("testing") +//@TestPropertySource( +// locations = "classpath:application-testing.yml") +public class ServiceMetricsApiControllerTest { + + @Autowired + private MockMvc mvc; + + @Autowired + ServiceRepoService serviceRepoService; + + @Autowired + private WebApplicationContext context; + + @Before + public void setup() throws Exception { + mvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) + @Test + public void testCountTotalServices() throws Exception { + createService(ServiceStateType.ACTIVE); + + String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/totalServices" ) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk() ) + .andReturn().getResponse().getContentAsString(); + + int totalServices = JsonPath.read(response, "$.totalServices"); + + + assertThat(totalServices).isEqualTo(serviceRepoService.findAll().size()); + } + + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) + @Test + public void testCountTotalServicesWithState() throws Exception { + createService(ServiceStateType.ACTIVE); + createService(ServiceStateType.INACTIVE); + + String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/totalServices" ) + .param("state", "ACTIVE") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk() ) + .andReturn().getResponse().getContentAsString(); + + int totalServices = JsonPath.read(response, "$.totalServices"); + + + List servicesList = serviceRepoService.findAll(); + int activeServices = (int) servicesList.stream().filter(service -> service.getState() == ServiceStateType.ACTIVE).count(); + + assertThat(totalServices).isEqualTo(activeServices); + assertThat(activeServices).isEqualTo(1); + } + + @WithMockUser(username = "osadmin", roles = {"ADMIN", "USER"}) + @Test + public void testGetServicesGroupedByState() throws Exception { + String startTime = OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT); + + createService(ServiceStateType.ACTIVE); + createService(ServiceStateType.ACTIVE); + createService(ServiceStateType.ACTIVE); + createService(ServiceStateType.INACTIVE); + createService(ServiceStateType.INACTIVE); + createService(ServiceStateType.TERMINATED); + + String endTime = OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT); + + String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/servicesGroupByState") + .param("starttime", startTime) + .param("endtime", endTime) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + List> groupByState = JsonPath.read(response, "$.services.aggregations.groupByState"); + + // Create a map from key -> count + Map stateCounts = groupByState.stream() + .collect(Collectors.toMap( + entry -> (String) entry.get("key"), + entry -> (Integer) entry.get("count") + )); + + assertThat(stateCounts.get("ACTIVE")).isEqualTo(3); + assertThat(stateCounts.get("INACTIVE")).isEqualTo(2); + assertThat(stateCounts.get("TERMINATED")).isEqualTo(1); + assertThat(stateCounts.get("FEASIBILITYCHECKED")).isEqualTo(0); + assertThat(stateCounts.get("RESERVED")).isEqualTo(0); + assertThat(stateCounts.get("DESIGNED")).isEqualTo(0); + } + + + + @Transactional + void createService(ServiceStateType state ) throws Exception { + int servicesCount = serviceRepoService.findAll().size(); + + File sspec = new File( "src/test/resources/testServiceSpec.json" ); + InputStream in = new FileInputStream( sspec ); + String sspectext = IOUtils.toString(in, "UTF-8"); + + ServiceSpecificationCreate sspeccr1 = JsonUtils.toJsonObj( sspectext, ServiceSpecificationCreate.class); + sspeccr1.setName("Spec1"); + ServiceSpecification responsesSpec = createServiceSpec(sspeccr1); + + ServiceCreate aService = new ServiceCreate(); + aService.setName("aNew Service"); + aService.setCategory("Test Category"); + aService.setDescription("A Test Service"); + aService.setStartDate( OffsetDateTime.now(ZoneOffset.UTC ).toString() ); + aService.setEndDate( OffsetDateTime.now(ZoneOffset.UTC ).toString() ); + aService.setState(state); + + Characteristic serviceCharacteristicItem = new Characteristic(); + + serviceCharacteristicItem.setName( "ConfigStatus" ); + serviceCharacteristicItem.setValue( new Any("NONE")); + aService.addServiceCharacteristicItem(serviceCharacteristicItem); + + ServiceSpecificationRef aServiceSpecificationRef = new ServiceSpecificationRef(); + aServiceSpecificationRef.setId(responsesSpec.getId() ); + aServiceSpecificationRef.setName(responsesSpec.getName()); + + aService.setServiceSpecificationRef(aServiceSpecificationRef ); + + String response = mvc.perform(MockMvcRequestBuilders.post("/serviceInventory/v4/service") + .with( SecurityMockMvcRequestPostProcessors.csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content( JsonUtils.toJson( aService ) )) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + Service responseService = JsonUtils.toJsonObj(response, Service.class); + + assertThat( serviceRepoService.findAll().size() ).isEqualTo( servicesCount + 1 ); + assertThat(responseService.getCategory()).isEqualTo("Test Category"); + assertThat(responseService.getDescription()).isEqualTo("A Test Service"); + + } + + private ServiceSpecification createServiceSpec(ServiceSpecificationCreate sspeccr1) throws Exception{ + String response = mvc.perform(MockMvcRequestBuilders.post("/serviceCatalogManagement/v4/serviceSpecification") + .with( SecurityMockMvcRequestPostProcessors.csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content( JsonUtils.toJson( sspeccr1 ) )) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + return JsonUtils.toJsonObj(response, ServiceSpecification.class); + } +} -- GitLab From 35f6de6e8821ab5638e1e135bf839ab19c9d1abc Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Tue, 6 May 2025 17:25:12 +0300 Subject: [PATCH 2/8] Removed duplicate 'tmf-api' prefix in metrics API endpoints --- .../java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java index da1c1174..91d6d20c 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java @@ -28,7 +28,7 @@ public interface ServiceMetricsApi { @ApiResponse(responseCode = "400", description = "Bad Request"), @ApiResponse(responseCode = "500", description = "Internal Server Error") }) - @RequestMapping(value = "/tmf-api/metrics/totalServices", method = RequestMethod.GET, produces = "application/json;charset=utf-8") + @RequestMapping(value = "/metrics/totalServices", method = RequestMethod.GET, produces = "application/json;charset=utf-8") ResponseEntity> getTotalServices( @Valid @RequestParam(value = "state", required = false) ServiceStateType state ); @@ -39,7 +39,7 @@ public interface ServiceMetricsApi { @ApiResponse(responseCode = "400", description = "Bad Request"), @ApiResponse(responseCode = "500", description = "Internal Server Error") }) - @RequestMapping(value = "/tmf-api/metrics/servicesGroupByState", method = RequestMethod.GET, produces = "application/json;charset=utf-8") + @RequestMapping(value = "/metrics/servicesGroupByState", method = RequestMethod.GET, produces = "application/json;charset=utf-8") ResponseEntity> getServicesGroupedByState( @Valid @RequestParam(value = "starttime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime starttime, @Valid @RequestParam(value = "endtime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime endtime -- GitLab From e66c7b4035bff5cf8cff25bd81a0fc5e1297e004 Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Tue, 6 May 2025 17:40:16 +0300 Subject: [PATCH 3/8] Removed duplicate 'tmf-api' prefix in unit tests --- .../api/metrics/ServiceMetricsApiControllerTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java index 2c0cc8d1..e7562ec4 100644 --- a/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java @@ -78,7 +78,7 @@ public class ServiceMetricsApiControllerTest { public void testCountTotalServices() throws Exception { createService(ServiceStateType.ACTIVE); - String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/totalServices" ) + String response = mvc.perform(MockMvcRequestBuilders.get("/metrics/totalServices" ) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk() ) .andReturn().getResponse().getContentAsString(); @@ -95,7 +95,7 @@ public class ServiceMetricsApiControllerTest { createService(ServiceStateType.ACTIVE); createService(ServiceStateType.INACTIVE); - String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/totalServices" ) + String response = mvc.perform(MockMvcRequestBuilders.get("/metrics/totalServices" ) .param("state", "ACTIVE") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk() ) @@ -125,7 +125,7 @@ public class ServiceMetricsApiControllerTest { String endTime = OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT); - String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/servicesGroupByState") + String response = mvc.perform(MockMvcRequestBuilders.get("/metrics/servicesGroupByState") .param("starttime", startTime) .param("endtime", endTime) .contentType(MediaType.APPLICATION_JSON)) -- GitLab From 7fc6f2dd0c2f580af81f9b7925d1f9fd53ecebce Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Wed, 7 May 2025 16:25:22 +0300 Subject: [PATCH 4/8] Changed structure to gather all metrics endpoints under one Controller --- .../api/{ServiceMetricsApi.java => MetricsApi.java} | 6 +++--- ...icsApiController.java => MetricsApiController.java} | 10 +++++----- ...MetricsRepoService.java => MetricsRepoService.java} | 2 +- ...ntrollerTest.java => MetricsApiControllerTest.java} | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) rename src/main/java/org/etsi/osl/tmf/metrics/api/{ServiceMetricsApi.java => MetricsApi.java} (94%) rename src/main/java/org/etsi/osl/tmf/metrics/api/{ServiceMetricsApiController.java => MetricsApiController.java} (88%) rename src/main/java/org/etsi/osl/tmf/metrics/reposervices/{ServiceMetricsRepoService.java => MetricsRepoService.java} (96%) rename src/test/java/org/etsi/osl/services/api/metrics/{ServiceMetricsApiControllerTest.java => MetricsApiControllerTest.java} (99%) diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java similarity index 94% rename from src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java rename to src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java index 91d6d20c..3793686b 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java @@ -17,10 +17,10 @@ import org.springframework.web.bind.annotation.RequestParam; import java.time.OffsetDateTime; import java.util.Map; -@Tag(name = "ServiceMetricsApi") -public interface ServiceMetricsApi { +@Tag(name = "MetricsApi") +public interface MetricsApi { - Logger log = LoggerFactory.getLogger(ServiceMetricsApi.class); + Logger log = LoggerFactory.getLogger(MetricsApi.class); @Operation(summary = "Get total number of services", operationId = "getTotalServices") @ApiResponses(value = { diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java similarity index 88% rename from src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java rename to src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java index 1c0a8007..e4e66b8f 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java @@ -1,7 +1,7 @@ package org.etsi.osl.tmf.metrics.api; import org.etsi.osl.tmf.common.model.service.ServiceStateType; -import org.etsi.osl.tmf.metrics.reposervices.ServiceMetricsRepoService; +import org.etsi.osl.tmf.metrics.reposervices.MetricsRepoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -16,13 +16,13 @@ import java.util.List; import java.util.Map; @Controller -public class ServiceMetricsApiController implements ServiceMetricsApi{ +public class MetricsApiController implements MetricsApi { - private static final Logger log = LoggerFactory.getLogger(ServiceMetricsApiController.class); - private final ServiceMetricsRepoService serviceMetricsRepoService; + private static final Logger log = LoggerFactory.getLogger(MetricsApiController.class); + private final MetricsRepoService serviceMetricsRepoService; @Autowired - public ServiceMetricsApiController(ServiceMetricsRepoService serviceMetricsRepoService) { + public MetricsApiController(MetricsRepoService serviceMetricsRepoService) { this.serviceMetricsRepoService = serviceMetricsRepoService; } diff --git a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java similarity index 96% rename from src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java rename to src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java index ef910dce..00894afb 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java @@ -12,7 +12,7 @@ import java.util.Map; import java.util.stream.Collectors; @Service -public class ServiceMetricsRepoService { +public class MetricsRepoService { @Autowired ObjectMapper objectMapper; diff --git a/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/metrics/MetricsApiControllerTest.java similarity index 99% rename from src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java rename to src/test/java/org/etsi/osl/services/api/metrics/MetricsApiControllerTest.java index e7562ec4..3586793b 100644 --- a/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/metrics/MetricsApiControllerTest.java @@ -54,7 +54,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @ActiveProfiles("testing") //@TestPropertySource( // locations = "classpath:application-testing.yml") -public class ServiceMetricsApiControllerTest { +public class MetricsApiControllerTest { @Autowired private MockMvc mvc; -- GitLab From 8fbbb42896b006f126955ebf8090499e46000e44 Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Mon, 12 May 2025 17:30:07 +0300 Subject: [PATCH 5/8] Use metrics models for API responses --- .../etsi/osl/tmf/metrics/api/MetricsApi.java | 6 ++-- .../tmf/metrics/api/MetricsApiController.java | 32 +++++++------------ 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java index 3793686b..eda13613 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java @@ -6,6 +6,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.etsi.osl.tmf.common.model.service.ServiceStateType; +import org.etsi.osl.tmf.metrics.ServicesGroupByState; +import org.etsi.osl.tmf.metrics.TotalServices; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.format.annotation.DateTimeFormat; @@ -29,7 +31,7 @@ public interface MetricsApi { @ApiResponse(responseCode = "500", description = "Internal Server Error") }) @RequestMapping(value = "/metrics/totalServices", method = RequestMethod.GET, produces = "application/json;charset=utf-8") - ResponseEntity> getTotalServices( + ResponseEntity getTotalServices( @Valid @RequestParam(value = "state", required = false) ServiceStateType state ); @@ -40,7 +42,7 @@ public interface MetricsApi { @ApiResponse(responseCode = "500", description = "Internal Server Error") }) @RequestMapping(value = "/metrics/servicesGroupByState", method = RequestMethod.GET, produces = "application/json;charset=utf-8") - ResponseEntity> getServicesGroupedByState( + ResponseEntity getServicesGroupedByState( @Valid @RequestParam(value = "starttime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime starttime, @Valid @RequestParam(value = "endtime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime endtime ); diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java index e4e66b8f..2e524784 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java @@ -1,6 +1,7 @@ package org.etsi.osl.tmf.metrics.api; import org.etsi.osl.tmf.common.model.service.ServiceStateType; +import org.etsi.osl.tmf.metrics.*; import org.etsi.osl.tmf.metrics.reposervices.MetricsRepoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,11 +28,10 @@ public class MetricsApiController implements MetricsApi { } @Override - public ResponseEntity> getTotalServices(ServiceStateType state) { + public ResponseEntity getTotalServices(ServiceStateType state) { try { int totalServices = serviceMetricsRepoService.countTotalServices(state); - Map response = new HashMap<>(); - response.put("totalServices", totalServices); + TotalServices response = new TotalServices(totalServices); return new ResponseEntity<>(response, HttpStatus.OK); } catch (Exception e) { log.error("Couldn't retrieve total services. ", e); @@ -40,7 +40,7 @@ public class MetricsApiController implements MetricsApi { } @Override - public ResponseEntity> getServicesGroupedByState(OffsetDateTime starttime, OffsetDateTime endtime) { + public ResponseEntity getServicesGroupedByState(OffsetDateTime starttime, OffsetDateTime endtime) { try { Map servicesByState = serviceMetricsRepoService.getServicesGroupedByState(starttime, endtime); @@ -55,25 +55,17 @@ public class MetricsApiController implements MetricsApi { fullStateMap.put(key.toUpperCase(), value); // normalize case just in case }); - // Build groupByState list - List> groupByStateList = fullStateMap.entrySet().stream() - .map(entry -> { - Map map = new HashMap<>(); - map.put("key", entry.getKey()); - map.put("count", entry.getValue()); - return map; - }) + // Create aggregation items + List groupByStateList = fullStateMap.entrySet().stream() + .map(entry -> new GroupByItem(entry.getKey(), entry.getValue())) .toList(); + // Build response structure using metrics models + GroupByStateAggregations aggregations = new GroupByStateAggregations(groupByStateList); + int total = fullStateMap.values().stream().mapToInt(Integer::intValue).sum(); + Services services = new Services(total, aggregations); + ServicesGroupByState response = new ServicesGroupByState(services); - // Wrap in response structure - Map aggregations = Map.of("groupByState", groupByStateList); - Map services = Map.of( - "total", fullStateMap.values().stream().mapToInt(Integer::intValue).sum(), - "aggregations", aggregations - ); - - Map response = Map.of("services", services); return new ResponseEntity<>(response, HttpStatus.OK); } catch (Exception e) { -- GitLab From ceb6e8e3dec5b3a1db412a98b41796620e83c2eb Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Wed, 14 May 2025 12:10:07 +0300 Subject: [PATCH 6/8] Use new version of metrics model --- .../org/etsi/osl/tmf/metrics/api/MetricsApiController.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java index 2e524784..70bd5a0b 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java @@ -11,7 +11,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import java.time.OffsetDateTime; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -56,12 +55,12 @@ public class MetricsApiController implements MetricsApi { }); // Create aggregation items - List groupByStateList = fullStateMap.entrySet().stream() - .map(entry -> new GroupByItem(entry.getKey(), entry.getValue())) + List groupByStateList = fullStateMap.entrySet().stream() + .map(entry -> new ServicesGroupByStateItem(ServiceStateType.valueOf(entry.getKey()), entry.getValue())) .toList(); // Build response structure using metrics models - GroupByStateAggregations aggregations = new GroupByStateAggregations(groupByStateList); + ServicesGroupByStateAggregations aggregations = new ServicesGroupByStateAggregations(groupByStateList); int total = fullStateMap.values().stream().mapToInt(Integer::intValue).sum(); Services services = new Services(total, aggregations); ServicesGroupByState response = new ServicesGroupByState(services); -- GitLab From 02e7a356c3cd223dc01f7d5f80b7d51bb850e45b Mon Sep 17 00:00:00 2001 From: Kostis Trantzas Date: Fri, 16 May 2025 02:00:13 +0300 Subject: [PATCH 7/8] renaming files --- .../metrics/api/{MetricsApi.java => ServiceMetricsApi.java} | 6 +++--- ...sApiController.java => ServiceMetricsApiController.java} | 0 ...tricsRepoService.java => ServiceMetricsRepoService.java} | 0 ...rollerTest.java => ServiceMetricsApiControllerTest.java} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename src/main/java/org/etsi/osl/tmf/metrics/api/{MetricsApi.java => ServiceMetricsApi.java} (92%) rename src/main/java/org/etsi/osl/tmf/metrics/api/{MetricsApiController.java => ServiceMetricsApiController.java} (100%) rename src/main/java/org/etsi/osl/tmf/metrics/reposervices/{MetricsRepoService.java => ServiceMetricsRepoService.java} (100%) rename src/test/java/org/etsi/osl/services/api/metrics/{MetricsApiControllerTest.java => ServiceMetricsApiControllerTest.java} (100%) diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java similarity index 92% rename from src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java rename to src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java index eda13613..b5595535 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApi.java @@ -19,10 +19,10 @@ import org.springframework.web.bind.annotation.RequestParam; import java.time.OffsetDateTime; import java.util.Map; -@Tag(name = "MetricsApi") -public interface MetricsApi { +@Tag(name = "ServiceMetricsApi", description = "The Services' Metrics API") +public interface ServiceMetricsApi { - Logger log = LoggerFactory.getLogger(MetricsApi.class); + Logger log = LoggerFactory.getLogger(ServiceMetricsApi.class); @Operation(summary = "Get total number of services", operationId = "getTotalServices") @ApiResponses(value = { diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java similarity index 100% rename from src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java rename to src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java diff --git a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java similarity index 100% rename from src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java rename to src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java diff --git a/src/test/java/org/etsi/osl/services/api/metrics/MetricsApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java similarity index 100% rename from src/test/java/org/etsi/osl/services/api/metrics/MetricsApiControllerTest.java rename to src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java -- GitLab From 724fcef378338602ff611630c60bc827d82e0b71 Mon Sep 17 00:00:00 2001 From: Kostis Trantzas Date: Fri, 16 May 2025 02:05:55 +0300 Subject: [PATCH 8/8] renaming files --- .../tmf/metrics/api/ServiceMetricsApiController.java | 10 +++++----- .../reposervices/ServiceMetricsRepoService.java | 2 +- .../api/metrics/ServiceMetricsApiControllerTest.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java index 70bd5a0b..abe7575e 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceMetricsApiController.java @@ -2,7 +2,7 @@ package org.etsi.osl.tmf.metrics.api; import org.etsi.osl.tmf.common.model.service.ServiceStateType; import org.etsi.osl.tmf.metrics.*; -import org.etsi.osl.tmf.metrics.reposervices.MetricsRepoService; +import org.etsi.osl.tmf.metrics.reposervices.ServiceMetricsRepoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -16,13 +16,13 @@ import java.util.List; import java.util.Map; @Controller -public class MetricsApiController implements MetricsApi { +public class ServiceMetricsApiController implements ServiceMetricsApi { - private static final Logger log = LoggerFactory.getLogger(MetricsApiController.class); - private final MetricsRepoService serviceMetricsRepoService; + private static final Logger log = LoggerFactory.getLogger(ServiceMetricsApiController.class); + private final ServiceMetricsRepoService serviceMetricsRepoService; @Autowired - public MetricsApiController(MetricsRepoService serviceMetricsRepoService) { + public ServiceMetricsApiController(ServiceMetricsRepoService serviceMetricsRepoService) { this.serviceMetricsRepoService = serviceMetricsRepoService; } diff --git a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java index 00894afb..ef910dce 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceMetricsRepoService.java @@ -12,7 +12,7 @@ import java.util.Map; import java.util.stream.Collectors; @Service -public class MetricsRepoService { +public class ServiceMetricsRepoService { @Autowired ObjectMapper objectMapper; diff --git a/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java index 3586793b..e7562ec4 100644 --- a/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/metrics/ServiceMetricsApiControllerTest.java @@ -54,7 +54,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @ActiveProfiles("testing") //@TestPropertySource( // locations = "classpath:application-testing.yml") -public class MetricsApiControllerTest { +public class ServiceMetricsApiControllerTest { @Autowired private MockMvc mvc; -- GitLab