diff --git a/pom.xml b/pom.xml index 6818795..176af75 100644 --- a/pom.xml +++ b/pom.xml @@ -295,6 +295,31 @@ 2.26.0 test + + + + org.springframework.boot + spring-boot-actuator + 2.1.18.RELEASE + + + org.springframework.boot + spring-boot-actuator-autoconfigure + 2.1.18.RELEASE + + + io.micrometer + micrometer-core + 1.7.2 + + + io.micrometer + micrometer-registry-prometheus + 1.7.2 + compile + + + diff --git a/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java b/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java index 67e5982..6f4a33d 100644 --- a/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java +++ b/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java @@ -60,11 +60,14 @@ public class AaiSecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { - http.csrf().disable() - .anonymous().disable() + http + .csrf().disable() .authorizeRequests() - .anyRequest().authenticated() + .regexMatchers("/actuator/.*").permitAll() + .regexMatchers("/metrics").permitAll() + .anyRequest().authenticated() .and() +// .anonymous().disable() .httpBasic() .authenticationEntryPoint(authenticationEntryPoint()) .and() diff --git a/src/main/java/eu/dnetlib/repo/manager/config/ActuatorConfig.java b/src/main/java/eu/dnetlib/repo/manager/config/ActuatorConfig.java new file mode 100644 index 0000000..875b2a1 --- /dev/null +++ b/src/main/java/eu/dnetlib/repo/manager/config/ActuatorConfig.java @@ -0,0 +1,39 @@ +package eu.dnetlib.repo.manager.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfiguration; +import org.springframework.boot.actuate.endpoint.MetricsEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; +import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import java.util.Collection; + +@Configuration +@EnableWebMvc +@Import({ + EndpointAutoConfiguration.class, + PublicMetricsAutoConfiguration.class, +// HealthIndicatorAutoConfiguration.class +}) + +public class ActuatorConfig { // TODO: remove this with migration to Spring Boot 2 + + @Bean + @Autowired + public EndpointHandlerMapping endpointHandlerMapping(Collection endpoints) { + return new EndpointHandlerMapping(endpoints); + } + + @Bean + @Autowired + public EndpointMvcAdapter metricsEndPoint(MetricsEndpoint delegate) { + return new EndpointMvcAdapter(delegate); + } +} diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/PrometheusController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/PrometheusController.java new file mode 100644 index 0000000..2024b2a --- /dev/null +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/PrometheusController.java @@ -0,0 +1,55 @@ +package eu.dnetlib.repo.manager.controllers; + +import eu.dnetlib.repo.manager.service.PiWikService; +import io.micrometer.core.instrument.binder.jvm.DiskSpaceMetrics; +import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics; +import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics; +import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics; +import io.micrometer.core.instrument.binder.system.ProcessorMetrics; +import io.micrometer.core.instrument.binder.system.UptimeMetrics; +import io.micrometer.prometheus.PrometheusConfig; +import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.prometheus.client.exporter.common.TextFormat; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.io.File; + + +@RestController +@RequestMapping("/actuator/prometheus") +public class PrometheusController { // TODO: remove this with migration to Spring Boot 2 + + private final PiWikService piWikService; + + @Autowired + public PrometheusController(PiWikService piWikService) { + this.piWikService = piWikService; + } + + @RequestMapping(method = RequestMethod.GET, path = "", produces = MediaType.TEXT_PLAIN_VALUE) + public String getPiwikMetrics() { + PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); + registry.counter("total").increment(piWikService.getTotal()); + registry.counter("validated").increment(piWikService.getValidated(true)); + + return registry.scrape(TextFormat.CONTENT_TYPE_004); + } + + @RequestMapping(method = RequestMethod.GET, path = "metrics", produces = MediaType.TEXT_PLAIN_VALUE) + public String getMetrics() { + PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); + new JvmThreadMetrics().bindTo(registry); + new JvmGcMetrics().bindTo(registry); + new JvmMemoryMetrics().bindTo(registry); + new DiskSpaceMetrics(new File("/")).bindTo(registry); + new ProcessorMetrics().bindTo(registry); // metrics related to the CPU stats + new UptimeMetrics().bindTo(registry); + + return registry.scrape(TextFormat.CONTENT_TYPE_004); + } + +} diff --git a/src/main/java/eu/dnetlib/repo/manager/service/PiWikService.java b/src/main/java/eu/dnetlib/repo/manager/service/PiWikService.java index 2dc3b5f..84f212e 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/PiWikService.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/PiWikService.java @@ -27,4 +27,8 @@ public interface PiWikService { ResponseEntity markPiwikSiteAsValidated(String repositoryId) throws RepositoryServiceException; PiwikInfo enableMetricsForRepository(String officialName, String repoWebsite, PiwikInfo piwikInfo) throws RepositoryServiceException; + + Integer getTotal(); + + Integer getValidated(boolean validated); } diff --git a/src/main/java/eu/dnetlib/repo/manager/service/PiWikServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/PiWikServiceImpl.java index a388ba4..4c3a995 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/PiWikServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/PiWikServiceImpl.java @@ -67,9 +67,9 @@ public class PiWikServiceImpl implements PiWikService { @Override public PiwikInfo getPiwikSiteForRepo(String repositoryId) { - try{ + try { return new JdbcTemplate(dataSource).queryForObject(GET_PIWIK_SITE, new String[]{repositoryId}, new int[]{Types.VARCHAR}, piwikRowMapper); - }catch (EmptyResultDataAccessException e){ + } catch (EmptyResultDataAccessException e) { return null; } } @@ -86,59 +86,59 @@ public class PiWikServiceImpl implements PiWikService { @Override public List getPiwikSitesForRepos(OrderByField orderByField, OrderByType orderByType, int from, int quantity, String searchField) { - try{ - String finalizedQuery = GET_PIWIK_SITES + " where ("+ + try { + String finalizedQuery = GET_PIWIK_SITES + " where (" + " repositoryid ilike ? " + " or siteid ilike ?" + " or requestorname ilike ?" + " or requestoremail ilike ?" + " or comment ilike ?" + - " or repositoryname ilike ?"+ + " or repositoryname ilike ?" + " or country ilike ?" - +") order by "+orderByField + " " + orderByType + " offset ? limit ?"; + + ") order by " + orderByField + " " + orderByType + " offset ? limit ?"; return new JdbcTemplate(dataSource).query(finalizedQuery, preparedStatement -> { - preparedStatement.setString(1,"%"+searchField+"%"); - preparedStatement.setString(2,"%"+searchField+"%"); - preparedStatement.setString(3,"%"+searchField+"%"); - preparedStatement.setString(4,"%"+searchField+"%"); - preparedStatement.setString(5,"%"+searchField+"%"); - preparedStatement.setString(6,"%"+searchField+"%"); - preparedStatement.setString(7,"%"+searchField+"%"); - preparedStatement.setInt(8,from); - preparedStatement.setInt(9,quantity); + preparedStatement.setString(1, "%" + searchField + "%"); + preparedStatement.setString(2, "%" + searchField + "%"); + preparedStatement.setString(3, "%" + searchField + "%"); + preparedStatement.setString(4, "%" + searchField + "%"); + preparedStatement.setString(5, "%" + searchField + "%"); + preparedStatement.setString(6, "%" + searchField + "%"); + preparedStatement.setString(7, "%" + searchField + "%"); + preparedStatement.setInt(8, from); + preparedStatement.setInt(9, quantity); }, piwikRowMapper); - }catch (EmptyResultDataAccessException e){ + } catch (EmptyResultDataAccessException e) { return null; } } @Override - public int getPiwikSitesTotals(String searchField){ - try{ - String finalizedQuery = GET_PIWIK_SITES_TOTAL + " where ("+ + public int getPiwikSitesTotals(String searchField) { + try { + String finalizedQuery = GET_PIWIK_SITES_TOTAL + " where (" + " repositoryid ilike ? " + " or siteid ilike ?" + " or requestorname ilike ?" + " or requestoremail ilike ?" + " or comment ilike ?" + - " or repositoryname ilike ?"+ + " or repositoryname ilike ?" + " or country ilike ?)"; return new JdbcTemplate(dataSource).query(finalizedQuery, preparedStatement -> { - preparedStatement.setString(1,"%"+searchField+"%"); - preparedStatement.setString(2,"%"+searchField+"%"); - preparedStatement.setString(3,"%"+searchField+"%"); - preparedStatement.setString(4,"%"+searchField+"%"); - preparedStatement.setString(5,"%"+searchField+"%"); - preparedStatement.setString(6,"%"+searchField+"%"); - preparedStatement.setString(7,"%"+searchField+"%"); - },rowMapper -> { + preparedStatement.setString(1, "%" + searchField + "%"); + preparedStatement.setString(2, "%" + searchField + "%"); + preparedStatement.setString(3, "%" + searchField + "%"); + preparedStatement.setString(4, "%" + searchField + "%"); + preparedStatement.setString(5, "%" + searchField + "%"); + preparedStatement.setString(6, "%" + searchField + "%"); + preparedStatement.setString(7, "%" + searchField + "%"); + }, rowMapper -> { rowMapper.next(); return rowMapper.getInt("totals"); }); - }catch (EmptyResultDataAccessException e){ + } catch (EmptyResultDataAccessException e) { return 0; } } @@ -146,8 +146,8 @@ public class PiWikServiceImpl implements PiWikService { @Override @PreAuthorize("hasAuthority('SUPER_ADMINISTRATOR') or hasAuthority('CONTENT_PROVIDER_DASHBOARD_ADMINISTRATOR')") public ResponseEntity approvePiwikSite(String repositoryId) { - new JdbcTemplate(dataSource).update(APPROVE_PIWIK_SITE, new Object[] {repositoryId}, new int[] {Types.VARCHAR}); - return new ResponseEntity<>("OK",HttpStatus.OK); + new JdbcTemplate(dataSource).update(APPROVE_PIWIK_SITE, new Object[]{repositoryId}, new int[]{Types.VARCHAR}); + return new ResponseEntity<>("OK", HttpStatus.OK); } @Override @@ -175,7 +175,7 @@ public class PiWikServiceImpl implements PiWikService { LOGGER.error("Error while sending email to administrator or user about the enabling of metrics", e); emailUtils.reportException(e); } - return new ResponseEntity<>("OK",HttpStatus.OK); + return new ResponseEntity<>("OK", HttpStatus.OK); } @Override @@ -188,7 +188,7 @@ public class PiWikServiceImpl implements PiWikService { + URLEncoder.encode(repoWebsite, "UTF-8"); Map map = new ObjectMapper().readValue(new URL(URL), Map.class); String siteId = null; - if(map.get("value")!=null) { + if (map.get("value") != null) { siteId = map.get("value").toString(); } piwikInfo.setSiteId(siteId); @@ -217,5 +217,14 @@ public class PiWikServiceImpl implements PiWikService { return piwikInfo; } + @Override + public Integer getTotal() { + return new JdbcTemplate(dataSource).queryForObject(GET_PIWIK_SITES_TOTAL, new Object[]{}, Integer.class); + } + @Override + public Integer getValidated(boolean validated) { + String finalizedQuery = GET_PIWIK_SITES_TOTAL + " where validated = ?"; + return new JdbcTemplate(dataSource).queryForObject(finalizedQuery, new Object[]{validated}, Integer.class); + } } diff --git a/src/test/java/metrics/PrometheusTest.java b/src/test/java/metrics/PrometheusTest.java new file mode 100644 index 0000000..7552e00 --- /dev/null +++ b/src/test/java/metrics/PrometheusTest.java @@ -0,0 +1,54 @@ +package metrics; + +import eu.dnetlib.repo.manager.controllers.PrometheusController; +import eu.dnetlib.repo.manager.service.PiWikService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +@Component +public class PrometheusTest { + + private static final int TOTAL = 10; + private static final int VALIDATED = 8; + + @Autowired + @Mock + PiWikService piWikService; + + @Autowired + @InjectMocks + private PrometheusController prometheusController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(piWikService.getTotal()).thenReturn(TOTAL); + when(piWikService.getValidated(true)).thenReturn(VALIDATED); + when(piWikService.getValidated(false)).thenReturn(TOTAL - VALIDATED); + } + + @Test + public void testMetrics() { + String report = prometheusController.getMetrics(); + assertTrue(report.startsWith("#")); + System.out.println(report); + } + + @Test + public void testPiwikMetrics() { + assertTrue(piWikService.getValidated(false).equals(TOTAL - VALIDATED)); + String report = prometheusController.getPiwikMetrics(); + assertTrue(report.contains("total_total " + TOTAL)); + assertTrue(report.contains("validated_total " + VALIDATED)); + System.out.println(report); + } +} \ No newline at end of file