Skip to content
Snippets Groups Projects
Commit 72b84e9c authored by Nikolaos Kyriakoulis's avatar Nikolaos Kyriakoulis
Browse files

Created metrics endpoints for TMP Resource related information

parent c6a2420c
No related branches found
No related tags found
2 merge requests!72pm628 measurementcollectionjob execution state not properly saved in the db,!69Resolve "Create metrics endpoints for TMF Resource related information"
Pipeline #13679 passed
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.ri639.model.ResourceStatusType;
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 = "ResourceMetricsApi")
public interface ResourceMetricsApi {
Logger log = LoggerFactory.getLogger(ResourceMetricsApi.class);
@Operation(summary = "Get total number of resources", operationId = "getTotalResources")
@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/totalResources", method = RequestMethod.GET, produces = "application/json;charset=utf-8")
ResponseEntity<Map<String, Integer>> getTotalResources(
@Valid @RequestParam(value = "state", required = false) ResourceStatusType state
);
@Operation(summary = "Get resources grouped by state", operationId = "getResourcesGroupedByState")
@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/resourcesGroupByState", method = RequestMethod.GET, produces = "application/json;charset=utf-8")
ResponseEntity<Map<String, Object>> getResourcesGroupedByState(
@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
);
}
package org.etsi.osl.tmf.metrics.api;
import org.etsi.osl.tmf.metrics.reposervices.ResourceMetricsRepoService;
import org.etsi.osl.tmf.ri639.model.ResourceStatusType;
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 ResourceMetricsApiController implements ResourceMetricsApi{
private static final Logger log = LoggerFactory.getLogger(ResourceMetricsApiController.class);
private final ResourceMetricsRepoService resourceMetricsRepoService;
@Autowired
public ResourceMetricsApiController(ResourceMetricsRepoService resourceMetricsRepoService) {
this.resourceMetricsRepoService = resourceMetricsRepoService;
}
@Override
public ResponseEntity<Map<String, Integer>> getTotalResources(ResourceStatusType state) {
try {
int totalResources = resourceMetricsRepoService.countTotalResources(state);
Map<String, Integer> response = new HashMap<>();
response.put("totalResources", totalResources);
return new ResponseEntity<>(response, HttpStatus.OK);
} catch (Exception e) {
log.error("Couldn't retrieve total resources. ", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Override
public ResponseEntity<Map<String, Object>> getResourcesGroupedByState(OffsetDateTime starttime, OffsetDateTime endtime) {
try {
Map<String, Integer> resourcesByState = resourceMetricsRepoService.getResourcesGroupedByState(starttime, endtime);
// Initialize with all possible states and 0. Ensures that all states are represented, even if not present in the data.
Map<String, Integer> fullStateMap = new LinkedHashMap<>();
for (ResourceStatusType state : ResourceStatusType.values()) {
fullStateMap.put(state.name(), 0); // default to 0
}
// Overwrite counts with actual data
resourcesByState.forEach((key, value) -> {
fullStateMap.put(key.toUpperCase(), value); // normalize case just in case
});
// Build groupByState list
List<Map<String, Object>> groupByStateList = fullStateMap.entrySet().stream()
.map(entry -> {
Map<String, Object> map = new HashMap<>();
map.put("key", entry.getKey());
map.put("count", entry.getValue());
return map;
})
.toList();
// Wrap in response structure
Map<String, Object> aggregations = Map.of("groupByState", groupByStateList);
Map<String, Object> services = Map.of(
"total", fullStateMap.values().stream().mapToInt(Integer::intValue).sum(),
"aggregations", aggregations
);
Map<String, Object> response = Map.of("resources", services);
return new ResponseEntity<>(response, HttpStatus.OK);
} catch (Exception e) {
log.error("Couldn't retrieve resources grouped by state. ", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
package org.etsi.osl.tmf.metrics.reposervices;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.etsi.osl.tmf.ri639.model.ResourceStatusType;
import org.etsi.osl.tmf.ri639.repo.ResourceRepository;
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 ResourceMetricsRepoService {
@Autowired
ObjectMapper objectMapper;
@Autowired
ResourceRepository resourceRepository;
public int countTotalResources(ResourceStatusType state) {
if (state == null) {
return resourceRepository.countAll();
} else {
return resourceRepository.countByResourceStatus(state);
}
}
public Map<String, Integer> getResourcesGroupedByState(OffsetDateTime starttime, OffsetDateTime endtime) {
if (starttime.plusDays(31).isBefore(endtime)) {
starttime = endtime.minusDays(31);
}
List<Object[]> rawResults = resourceRepository.groupByStateBetweenDates(starttime, endtime);
return rawResults.stream()
.collect(Collectors.toMap(
row -> row[0].toString(),
row -> ((Number) row[1]).intValue()
));
}
}
......@@ -19,9 +19,11 @@
*/
package org.etsi.osl.tmf.ri639.repo;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Optional;
import org.etsi.osl.tmf.ri639.model.Resource;
import org.etsi.osl.tmf.ri639.model.ResourceStatusType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
......@@ -56,4 +58,18 @@ public interface ResourceRepository extends JpaRepository<Resource, Long> {
List<Resource> findByNameAndResourceVersion(String aname, String aversion);
List<Resource> findByNameAndCategoryAndResourceVersion(String aname, String acategory, String aversion);
// Methods for metrics
@Query("SELECT COUNT(res) FROM RIResource res")
int countAll();
int countByResourceStatus(ResourceStatusType status);
@Query("SELECT res.resourceStatus, COUNT(res) FROM RIResource res "
+ "WHERE res.startOperatingDate >= :starttime AND res.endOperatingDate <= :endtime "
+ "GROUP BY res.resourceStatus")
List<Object[]> groupByStateBetweenDates(OffsetDateTime starttime, OffsetDateTime endtime);
}
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.UserPartRoleType;
import org.etsi.osl.tmf.common.model.service.Note;
import org.etsi.osl.tmf.common.model.service.ResourceRef;
import org.etsi.osl.tmf.prm669.model.RelatedParty;
import org.etsi.osl.tmf.rcm634.model.*;
import org.etsi.osl.tmf.ri639.model.*;
import org.etsi.osl.tmf.ri639.reposervices.ResourceRepoService;
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.net.URI;
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 ResourceMetricsApiControllerTest {
@Autowired
private MockMvc mvc;
@Autowired
ResourceRepoService resourceRepoService;
@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 testCountTotalResources() throws Exception {
createResource(ResourceStatusType.AVAILABLE);
String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/totalResources" )
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk() )
.andReturn().getResponse().getContentAsString();
int totalResources = JsonPath.read(response, "$.totalResources");
assertThat(totalResources).isEqualTo(resourceRepoService.findAll().size());
}
@WithMockUser(username="osadmin", roles = {"ADMIN","USER"})
@Test
public void testCountTotalServicesWithState() throws Exception {
createResource(ResourceStatusType.AVAILABLE);
createResource(ResourceStatusType.STANDBY);
String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/totalResources" )
.param("state", "STANDBY")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk() )
.andReturn().getResponse().getContentAsString();
int totalResources = JsonPath.read(response, "$.totalResources");
List<Resource> resourcesList = resourceRepoService.findAll();
int standByResources = (int) resourcesList.stream().filter(resource -> resource.getResourceStatus() == ResourceStatusType.STANDBY).count();
assertThat(totalResources).isEqualTo(standByResources);
assertThat(totalResources).isEqualTo(1);
}
@WithMockUser(username = "osadmin", roles = {"ADMIN", "USER"})
@Test
public void testGetServicesGroupedByState() throws Exception {
String startTime = OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT);
createResource(ResourceStatusType.AVAILABLE);
createResource(ResourceStatusType.AVAILABLE);
createResource(ResourceStatusType.AVAILABLE);
createResource(ResourceStatusType.RESERVED);
createResource(ResourceStatusType.RESERVED);
createResource(ResourceStatusType.STANDBY);
String endTime = OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT);
String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/resourcesGroupByState")
.param("starttime", startTime)
.param("endtime", endTime)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
List<Map<String, Object>> groupByState = JsonPath.read(response, "$.resources.aggregations.groupByState");
// Create a map from key -> count
Map<String, Integer> stateCounts = groupByState.stream()
.collect(Collectors.toMap(
entry -> (String) entry.get("key"),
entry -> (Integer) entry.get("count")
));
assertThat(stateCounts.get("AVAILABLE")).isEqualTo(3);
assertThat(stateCounts.get("RESERVED")).isEqualTo(2);
assertThat(stateCounts.get("STANDBY")).isEqualTo(1);
assertThat(stateCounts.get("ALARM")).isEqualTo(0);
assertThat(stateCounts.get("UNKNOWN")).isEqualTo(0);
assertThat(stateCounts.get("SUSPENDED")).isEqualTo(0);
}
private void createResource(ResourceStatusType statusType) throws Exception{
/**
* first add 2 specs
*/
File sspec = new File( "src/test/resources/testResourceSpec.json" );
InputStream in = new FileInputStream( sspec );
String sspectext = IOUtils.toString(in, "UTF-8");
ResourceSpecificationCreate sspeccr1 = JsonUtils.toJsonObj( sspectext, ResourceSpecificationCreate.class);
sspeccr1.setName("Spec1");
ResourceSpecification responsesSpec1 = createResourceSpec( sspeccr1);
//res 2 is an RFS
ResourceSpecificationCreate sspeccr2 = JsonUtils.toJsonObj( sspectext, ResourceSpecificationCreate.class);
sspeccr2.setName("Spec2");
sspeccr2.addResourceSpecificationRelationshipWith( responsesSpec1 );
LogicalResourceSpecification responsesSpec2 = (LogicalResourceSpecification) createResourceSpec( sspeccr2 );
/**
* add them as bundle
*/
ResourceSpecificationCreate sspeccr3 = JsonUtils.toJsonObj( sspectext, ResourceSpecificationCreate.class);
sspeccr3.setName("BundleExampleSpec");
sspeccr3.isBundle(true);
sspeccr3.addResourceSpecificationRelationshipWith( responsesSpec1 );
sspeccr3.addResourceSpecificationRelationshipWith( responsesSpec2 );
ResourceSpecification responsesSpec3 = createResourceSpec( sspeccr3);
ResourceCreate aResource = new ResourceCreate();
aResource.setName("aNew Resource parent");
aResource.setCategory("Experimentation");
aResource.setDescription("Experimentation Descr");
aResource.setStartOperatingDate( OffsetDateTime.now(ZoneOffset.UTC ).toString() );
aResource.setEndOperatingDate( OffsetDateTime.now(ZoneOffset.UTC ).toString() );
aResource.setResourceStatus(statusType);
Note noteItem = new Note();
noteItem.text("test note");
aResource.addNoteItem(noteItem);
Characteristic resCharacteristicItem = new Characteristic();
resCharacteristicItem.setName( "externalPartnerServiceId" );
resCharacteristicItem.setValue( new Any("NONE"));
aResource.addResourceCharacteristicItem(resCharacteristicItem);
ResourceSpecificationRef aServiceSpecificationRef = new ResourceSpecificationRef();
aServiceSpecificationRef.setId(responsesSpec3.getId() );
aServiceSpecificationRef.setName(responsesSpec3.getName());
aResource.setResourceSpecification( aServiceSpecificationRef );
resourceRepoService.addResource(aResource);
}
private ResourceSpecification createResourceSpec(ResourceSpecificationUpdate sspeccr1) throws Exception{
URI url = new URI("/resourceCatalogManagement/v4/resourceSpecification");
if (sspeccr1 instanceof PhysicalResourceSpecificationUpdate ) {
url = new URI("/resourceCatalogManagement/v4/resourceSpecification");
}
String responseSpec = mvc.perform(MockMvcRequestBuilders.post( url )
.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();
ResourceSpecification responsesSpec1;
if (sspeccr1 instanceof PhysicalResourceSpecificationUpdate ) {
responsesSpec1 = JsonUtils.toJsonObj(responseSpec, PhysicalResourceSpecification.class);
}else {
responsesSpec1 = JsonUtils.toJsonObj(responseSpec, LogicalResourceSpecification.class);
}
return responsesSpec1;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment