diff --git a/.gitignore b/.gitignore index 63fad60d2101db6bb950496d400fe40430dcc165..4a7741ceb00f3c5bc0d3c960e37539669403d0d3 100644 --- a/.gitignore +++ b/.gitignore @@ -90,4 +90,6 @@ buildNumber.properties /application.properties docker/data docker/data/** +docker/prom_data/** +docker/*-db .env diff --git a/airport-manager-api/src/main/java/cz/muni/fi/pa165/api/employee/Employee.java b/airport-manager-api/src/main/java/cz/muni/fi/pa165/api/employee/Employee.java index 244724d7ac29f51ae41d1d0a36980c78c8e947a1..76a09c0007c404f83c78c8e98dde43f32e1fc91a 100644 --- a/airport-manager-api/src/main/java/cz/muni/fi/pa165/api/employee/Employee.java +++ b/airport-manager-api/src/main/java/cz/muni/fi/pa165/api/employee/Employee.java @@ -5,12 +5,13 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import java.io.Serializable; import java.time.LocalDate; import java.util.Objects; import java.util.UUID; @Data -public class Employee { +public class Employee implements Serializable { private UUID id; private String name; diff --git a/airports-flight-service/pom.xml b/airports-flight-service/pom.xml index 31dcf37d58e2c2d499183195daa25a113bddb4bf..5b7050342df20a78eea22a89901dcc05ca79ac47 100644 --- a/airports-flight-service/pom.xml +++ b/airports-flight-service/pom.xml @@ -30,10 +30,18 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> + </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> diff --git a/airports-flight-service/src/main/java/cz/muni/fi/pa165/SwaggerConfig.java b/airports-flight-service/src/main/java/cz/muni/fi/pa165/SwaggerConfig.java index 62e64199658802c17cd6c954e52e87a9370b247e..d0aa68b54ee1d9cfd6c78bc4738062da621a082c 100644 --- a/airports-flight-service/src/main/java/cz/muni/fi/pa165/SwaggerConfig.java +++ b/airports-flight-service/src/main/java/cz/muni/fi/pa165/SwaggerConfig.java @@ -1,17 +1,70 @@ package cz.muni.fi.pa165; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider; +import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider; import springfox.documentation.swagger2.annotations.EnableSwagger2; +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; + + @Configuration @EnableSwagger2 public class SwaggerConfig { + @Bean // This bean is required when you want swagger and actuator to work together + public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() { + return new BeanPostProcessor() { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) { + customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); + } + return bean; + } + + private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings( + List<T> mappings) { + List<T> copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null) + .collect(Collectors.toList()); + mappings.clear(); + mappings.addAll(copy); + } + + @SuppressWarnings("unchecked") + private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) { + try { + Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); + field.setAccessible(true); + return (List<RequestMappingInfoHandlerMapping>) field.get(bean); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalStateException(e); + } + } + }; + } + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.regex("/manage.*")) + .build(); + } + @Bean public Docket facadesV1Api() { return createDocket() @@ -27,4 +80,5 @@ public class SwaggerConfig { private Docket createDocket() { return new Docket(DocumentationType.SWAGGER_2); } + } diff --git a/airports-flight-service/src/main/java/cz/muni/fi/pa165/observations/health/CustomHealthIndicator.java b/airports-flight-service/src/main/java/cz/muni/fi/pa165/observations/health/CustomHealthIndicator.java new file mode 100644 index 0000000000000000000000000000000000000000..cab6cb55ebc8bf3f0c50d33f8a9122e5db856410 --- /dev/null +++ b/airports-flight-service/src/main/java/cz/muni/fi/pa165/observations/health/CustomHealthIndicator.java @@ -0,0 +1,23 @@ +package cz.muni.fi.pa165.observations.health; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +@Component +public class CustomHealthIndicator implements HealthIndicator { + + @Autowired + private HealthProvider healthProvider; + + @Override + public Health health() { + boolean systemHealth = healthProvider.getSystemHealth(); + if (systemHealth) { + return Health.up().build(); + } else { + return Health.down().withDetail("error", "injected failure").build(); + } + } +} \ No newline at end of file diff --git a/airports-flight-service/src/main/java/cz/muni/fi/pa165/observations/health/HealthProvider.java b/airports-flight-service/src/main/java/cz/muni/fi/pa165/observations/health/HealthProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..31959583ea4a042aebeefe52d8b20f0cc73a8fee --- /dev/null +++ b/airports-flight-service/src/main/java/cz/muni/fi/pa165/observations/health/HealthProvider.java @@ -0,0 +1,15 @@ +package cz.muni.fi.pa165.observations.health; + +import org.springframework.stereotype.Component; + +import java.util.concurrent.atomic.AtomicInteger; + +@Component +public class HealthProvider { + + private AtomicInteger counter = new AtomicInteger(0); + + public boolean getSystemHealth() { + return counter.getAndIncrement() % 2 == 0; + } +} diff --git a/airports-flight-service/src/main/java/cz/muni/fi/pa165/observations/info/CustomInfoContributor.java b/airports-flight-service/src/main/java/cz/muni/fi/pa165/observations/info/CustomInfoContributor.java new file mode 100644 index 0000000000000000000000000000000000000000..903740f24a508116796932f5f45301ac141fcde3 --- /dev/null +++ b/airports-flight-service/src/main/java/cz/muni/fi/pa165/observations/info/CustomInfoContributor.java @@ -0,0 +1,14 @@ +package cz.muni.fi.pa165.observations.info; + +import org.springframework.boot.actuate.info.Info.Builder; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.stereotype.Component; + +@Component +public class CustomInfoContributor implements InfoContributor { + + @Override + public void contribute(Builder builder) { + builder.withDetail("course", "PA165"); + } +} diff --git a/airports-flight-service/src/main/resources/application.properties b/airports-flight-service/src/main/resources/application.properties index b6370b98faf8695aaa74fa2e56b2af4a62fedf24..debf240735b6a373aa52a6a8cd6560de43c59865 100644 --- a/airports-flight-service/src/main/resources/application.properties +++ b/airports-flight-service/src/main/resources/application.properties @@ -27,21 +27,14 @@ application-version=1.0 logging.level.org.springframework.web=DEBUG logging.level.io.springfox=DEBUG +management.endpoints.web.exposure.include=info,health,metrics,loggers,prometheus +management.endpoint.health.show-details=always +management.endpoint.health.probes.enabled=true +management.endpoint.info.enabled=true +management.endpoint.env.enabled=true +management.context-path=/manage +management.endpoints.web.base-path=/manage -#spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect -#spring.jpa.hibernate.ddl-auto=update -#spring.jpa.generate-ddl=true -#spring.jpa.show-sql=true -#spring.datasource.driver-class-name=org.postgresql.Driver -#spring.datasource.url=jdbc:postgresql://localhost:5432/airport-manager -#spring.datasource.username=postgres -#spring.datasource.password=root -#spring.jpa.properties.hibernate.hbm2ddl.create_namespaces=true -#jakarta.peristence.create-database-schemas=true -# -#springdoc.swagger-ui.path=/swagger-ui.html -#application-description=API Description -#application-version=1.0 -# -#logging.level.org.springframework.web=DEBUG -#logging.level.io.springfox=DEBUG \ No newline at end of file +info.app.encoding=UTF-8 +info.app.java.source=17 +info.app.java.target=17 \ No newline at end of file diff --git a/airports-hr-service/pom.xml b/airports-hr-service/pom.xml index 9a74c3f3a0ae391a9276d931858c5448f524d881..183cd6760476fe85dfe974614ac13501fbefbb7f 100644 --- a/airports-hr-service/pom.xml +++ b/airports-hr-service/pom.xml @@ -37,10 +37,18 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> + </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> diff --git a/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/SwaggerConfig.java b/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/SwaggerConfig.java index 2cdfc4b376fcb0759e6272520f95e63739562478..08c6e6dc010b9a3942e114557400fe2dc6fbcbd4 100644 --- a/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/SwaggerConfig.java +++ b/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/SwaggerConfig.java @@ -1,17 +1,70 @@ package cz.muni.fi.pa165.hr; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider; +import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider; import springfox.documentation.swagger2.annotations.EnableSwagger2; +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; + + @Configuration @EnableSwagger2 public class SwaggerConfig { + @Bean // This bean is required when you want swagger and actuator to work together + public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() { + return new BeanPostProcessor() { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) { + customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); + } + return bean; + } + + private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings( + List<T> mappings) { + List<T> copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null) + .collect(Collectors.toList()); + mappings.clear(); + mappings.addAll(copy); + } + + @SuppressWarnings("unchecked") + private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) { + try { + Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); + field.setAccessible(true); + return (List<RequestMappingInfoHandlerMapping>) field.get(bean); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalStateException(e); + } + } + }; + } + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.regex("/manage.*")) + .build(); + } + @Bean public Docket facadesV1Api() { return createDocket() @@ -26,4 +79,5 @@ public class SwaggerConfig { private Docket createDocket() { return new Docket(DocumentationType.SWAGGER_2); } + } \ No newline at end of file diff --git a/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/observations/health/CustomHealthIndicator.java b/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/observations/health/CustomHealthIndicator.java new file mode 100644 index 0000000000000000000000000000000000000000..7d86997f753d7513c919ed8c144a4507a1aa6342 --- /dev/null +++ b/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/observations/health/CustomHealthIndicator.java @@ -0,0 +1,23 @@ +package cz.muni.fi.pa165.hr.observations.health; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +@Component +public class CustomHealthIndicator implements HealthIndicator { + + @Autowired + private HealthProvider healthProvider; + + @Override + public Health health() { + boolean systemHealth = healthProvider.getSystemHealth(); + if (systemHealth) { + return Health.up().build(); + } else { + return Health.down().withDetail("error", "injected failure").build(); + } + } +} diff --git a/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/observations/health/HealthProvider.java b/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/observations/health/HealthProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..c756864d410e0eaac9e1067ca70869cd8cecca09 --- /dev/null +++ b/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/observations/health/HealthProvider.java @@ -0,0 +1,15 @@ +package cz.muni.fi.pa165.hr.observations.health; + +import org.springframework.stereotype.Component; + +import java.util.concurrent.atomic.AtomicInteger; + +@Component +public class HealthProvider { + + private AtomicInteger counter = new AtomicInteger(0); + + public boolean getSystemHealth() { + return counter.getAndIncrement() % 2 == 0; + } +} diff --git a/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/observations/info/CustomInfoContributor.java b/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/observations/info/CustomInfoContributor.java new file mode 100644 index 0000000000000000000000000000000000000000..1a8bbdb94ad0d0687b46c76bca3f593c40cfd7c6 --- /dev/null +++ b/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/observations/info/CustomInfoContributor.java @@ -0,0 +1,14 @@ +package cz.muni.fi.pa165.hr.observations.info; + +import org.springframework.boot.actuate.info.Info.Builder; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.stereotype.Component; + +@Component +public class CustomInfoContributor implements InfoContributor { + + @Override + public void contribute(Builder builder) { + builder.withDetail("course", "PA165"); + } +} diff --git a/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/rest/EmployeeController.java b/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/rest/EmployeeController.java index a86a7b7921f4e5eaec01bbba17b929acdb92d342..7b8e266699852faa173bdac48d96ce35837bbfce 100644 --- a/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/rest/EmployeeController.java +++ b/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/rest/EmployeeController.java @@ -4,6 +4,7 @@ import cz.muni.fi.pa165.api.employee.requests.EmployeeRequest; import cz.muni.fi.pa165.api.employee.Employee; import cz.muni.fi.pa165.hr.facade.EmployeeFacade; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import javax.annotation.PostConstruct; @@ -15,7 +16,7 @@ public class EmployeeController { private final EmployeeFacade employeeFacade; - @GetMapping("/employee/{id}") + @GetMapping(path = "/employee/{id}") public Employee get(@PathVariable UUID id) { return employeeFacade.get(id); } diff --git a/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/service/EmployeeService.java b/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/service/EmployeeService.java index 26e596c3c1e0ff4ebea25ef5b016568a1d295fdf..28d0aedea811d0487bb81e922f98e3f6fbdf0eef 100644 --- a/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/service/EmployeeService.java +++ b/airports-hr-service/src/main/java/cz/muni/fi/pa165/hr/service/EmployeeService.java @@ -33,7 +33,8 @@ public class EmployeeService { } public Employee get(UUID id) { - return employeeRepository.findById(id).orElseThrow(EmployeeNotFoundException::new); + var result = employeeRepository.findById(id).orElseThrow(EmployeeNotFoundException::new); + return result; } public Employee updateEmployee(UUID id, Employee employeeUpdate) { diff --git a/airports-hr-service/src/main/resources/application.properties b/airports-hr-service/src/main/resources/application.properties index 0e013ec7a6565ee630691c5186914a3d2a978e58..c0ba2135525c49153c3149b5091a899a518c0492 100644 --- a/airports-hr-service/src/main/resources/application.properties +++ b/airports-hr-service/src/main/resources/application.properties @@ -14,4 +14,16 @@ application-description=API Description application-version=1.0 logging.level.org.springframework.web=DEBUG -logging.level.io.springfox=DEBUG \ No newline at end of file +logging.level.io.springfox=DEBUG + +management.endpoints.web.exposure.include=info,health,metrics,loggers,prometheus +management.endpoint.health.show-details=always +management.endpoint.health.probes.enabled=true +management.endpoint.info.enabled=true +management.endpoint.env.enabled=true +management.context-path=/manage +management.endpoints.web.base-path=/manage + +info.app.encoding=UTF-8 +info.app.java.source=17 +info.app.java.target=17 \ No newline at end of file diff --git a/airports-hr-service/src/test/java/cz/muni/fi/pa165/hr/integration/EmployeeIntegrationTest.java b/airports-hr-service/src/test/java/cz/muni/fi/pa165/hr/integration/EmployeeIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c380172a92874e06a0abc8c9dd8fdba020bad145 --- /dev/null +++ b/airports-hr-service/src/test/java/cz/muni/fi/pa165/hr/integration/EmployeeIntegrationTest.java @@ -0,0 +1,76 @@ +package cz.muni.fi.pa165.hr.integration; + +import cz.muni.fi.pa165.api.employee.Employee; +import cz.muni.fi.pa165.hr.Application; +import cz.muni.fi.pa165.hr.facade.EmployeeFacade; +import cz.muni.fi.pa165.hr.repository.EmployeeRepository; +import cz.muni.fi.pa165.hr.repository.PilotRepository; +import cz.muni.fi.pa165.hr.repository.StewardRepository; +import cz.muni.fi.pa165.hr.rest.EmployeeController; +import cz.muni.fi.pa165.hr.service.EmployeeService; +import cz.muni.fi.pa165.hr.util.ObjectConverter; +import cz.muni.fi.pa165.hr.util.TestApiFactory; +import cz.muni.fi.pa165.hr.util.TestDaoFactory; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(classes = {EmployeeService.class, EmployeeController.class}) +//@SpringBootTest(classes = {Application.class}) +@AutoConfigureMockMvc +public class EmployeeIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private EmployeeRepository employeeRepository; + + @MockBean + private EmployeeFacade employeeFacade; + + + @MockBean + private PilotRepository pilotRepository; + + @MockBean + private StewardRepository stewardRepository; + +// @Test + void findById_employeeFound_returnsEmployee() throws Exception { + UUID id = new UUID(0x1, 0xf); + var employee = TestDaoFactory.getEmployeeEntity(); +// Mockito.when(employeeRScreenshot from 2024-04-30 19-09-51epository.findById(id)).thenReturn(Optional.of(employee)); + Mockito.when(employeeFacade.get(id)).thenReturn(TestApiFactory.getEmployeeEntity()); + + String responseJson = mockMvc.perform(get("/employee/{id}", id) + .accept(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(StandardCharsets.UTF_8); +// String responseJson = mockMvc.perform(get("/employee/{id}", id) +// .accept(MediaType.APPLICATION_JSON_VALUE)) +// .andExpect(status().isOk()) +// .andReturn() +// .getResponse() +// .getContentAsString(StandardCharsets.UTF_8); +// Employee response = ObjectConverter.convertJsonToObject(responseJson, Employee.class); + + assertThat(responseJson).isEqualTo("{\"id\":\"00000000-0000-0001-0000-00000000000f\",\"name\":\"Name\",\"surname\":\"Surname\",\"gender\":true,\"dateOfBirth\":\"1980-05-15\",\"hired\":\"2010-05-15\",\"terminated\":null}"); + } +} diff --git a/airports-planes-service/pom.xml b/airports-planes-service/pom.xml index 4ce60142b181fcc774997150b4c4fa2594ca5aab..ca8382228f5a6fb92fdcd6acb5ec47d49c166a51 100644 --- a/airports-planes-service/pom.xml +++ b/airports-planes-service/pom.xml @@ -36,10 +36,18 @@ <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> + </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> diff --git a/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/SwaggerConfig.java b/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/SwaggerConfig.java index 40650a5319f4ae658bdbe4ea65b9c2bf1330cabb..99e094a3fa9ee94d9e64b6bf119b406bff6f2b5d 100644 --- a/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/SwaggerConfig.java +++ b/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/SwaggerConfig.java @@ -1,18 +1,61 @@ package cz.muni.fi.pa165.planes; import cz.muni.fi.pa165.planes.rest.PlaneController; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider; +import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider; import springfox.documentation.swagger2.annotations.EnableSwagger2; +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; + @Configuration @EnableSwagger2 public class SwaggerConfig { + @Bean // This bean is required when you want swagger and actuator to work together + public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() { + return new BeanPostProcessor() { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) { + customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); + } + return bean; + } + + private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings( + List<T> mappings) { + List<T> copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null) + .collect(Collectors.toList()); + mappings.clear(); + mappings.addAll(copy); + } + + @SuppressWarnings("unchecked") + private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) { + try { + Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); + field.setAccessible(true); + return (List<RequestMappingInfoHandlerMapping>) field.get(bean); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalStateException(e); + } + } + }; + } + @Bean public Docket facadesV1Api() { return createDocket() @@ -24,6 +67,15 @@ public class SwaggerConfig { .build(); } + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.regex("/manage.*")) + .build(); + } + private Docket createDocket() { return new Docket(DocumentationType.SWAGGER_2); } diff --git a/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/observations/health/CustomHealthIndicator.java b/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/observations/health/CustomHealthIndicator.java new file mode 100644 index 0000000000000000000000000000000000000000..f32065029463fefed96e6ed33080ad6648d779fe --- /dev/null +++ b/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/observations/health/CustomHealthIndicator.java @@ -0,0 +1,23 @@ +package cz.muni.fi.pa165.planes.observations.health; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +@Component +public class CustomHealthIndicator implements HealthIndicator { + + @Autowired + private HealthProvider healthProvider; + + @Override + public Health health() { + boolean systemHealth = healthProvider.getSystemHealth(); + if (systemHealth) { + return Health.up().build(); + } else { + return Health.down().withDetail("error", "injected failure").build(); + } + } +} diff --git a/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/observations/health/HealthProvider.java b/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/observations/health/HealthProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..e260e08773126c4d1366097594f022416e926778 --- /dev/null +++ b/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/observations/health/HealthProvider.java @@ -0,0 +1,16 @@ +package cz.muni.fi.pa165.planes.observations.health; + +import org.springframework.stereotype.Component; + +import java.util.concurrent.atomic.AtomicInteger; + +@Component +public class HealthProvider { + + private AtomicInteger counter = new AtomicInteger(0); + + public boolean getSystemHealth() { + return counter.getAndIncrement() % 2 == 0; + } +} + diff --git a/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/observations/info/CustomInfoContributor.java b/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/observations/info/CustomInfoContributor.java new file mode 100644 index 0000000000000000000000000000000000000000..ff4d088b5b7a9b5f24b79ac9c6a05c361ea2a4cf --- /dev/null +++ b/airports-planes-service/src/main/java/cz/muni/fi/pa165/planes/observations/info/CustomInfoContributor.java @@ -0,0 +1,14 @@ +package cz.muni.fi.pa165.planes.observations.info; + +import org.springframework.boot.actuate.info.Info.Builder; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.stereotype.Component; + +@Component +public class CustomInfoContributor implements InfoContributor { + + @Override + public void contribute(Builder builder) { + builder.withDetail("course", "PA165"); + } +} diff --git a/airports-planes-service/src/main/resources/application.properties b/airports-planes-service/src/main/resources/application.properties index 0e013ec7a6565ee630691c5186914a3d2a978e58..e588dcbe5e70a6b992a3b0aae14c10030bb782c4 100644 --- a/airports-planes-service/src/main/resources/application.properties +++ b/airports-planes-service/src/main/resources/application.properties @@ -14,4 +14,16 @@ application-description=API Description application-version=1.0 logging.level.org.springframework.web=DEBUG -logging.level.io.springfox=DEBUG \ No newline at end of file +logging.level.io.springfox=DEBUG + +management.endpoints.web.exposure.include=info,health,metrics,loggers,prometheus +management.endpoint.health.show-details=always +management.endpoint.health.probes.enabled=true +management.endpoint.info.enabled=true +management.endpoint.env.enabled=true +management.context-path=/manage +management.endpoints.web.base-path=/manage + +info.app.encoding=UTF-8 +info.app.java.source=17 +info.app.java.target=17 diff --git a/airports-service/pom.xml b/airports-service/pom.xml index 0ee0bf9218e6dc9c9dd6b52317ab1d11b86ccbc8..8645e8d5e39482dc4f2a4dba319850278b43d67e 100644 --- a/airports-service/pom.xml +++ b/airports-service/pom.xml @@ -39,6 +39,10 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> diff --git a/airports-service/src/main/java/cz/muni/fi/pa165/airport/SwaggerConfig.java b/airports-service/src/main/java/cz/muni/fi/pa165/airport/SwaggerConfig.java index cba41b5e286b4fb211283152f31a7af40f249c12..6ffba8ea2def8e9b4a93598a9663044fd461dc62 100644 --- a/airports-service/src/main/java/cz/muni/fi/pa165/airport/SwaggerConfig.java +++ b/airports-service/src/main/java/cz/muni/fi/pa165/airport/SwaggerConfig.java @@ -1,17 +1,70 @@ package cz.muni.fi.pa165.airport; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider; +import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider; import springfox.documentation.swagger2.annotations.EnableSwagger2; +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; + + @Configuration @EnableSwagger2 public class SwaggerConfig { + @Bean // This bean is required when you want swagger and actuator to work together + public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() { + return new BeanPostProcessor() { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) { + customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); + } + return bean; + } + + private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings( + List<T> mappings) { + List<T> copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null) + .collect(Collectors.toList()); + mappings.clear(); + mappings.addAll(copy); + } + + @SuppressWarnings("unchecked") + private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) { + try { + Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); + field.setAccessible(true); + return (List<RequestMappingInfoHandlerMapping>) field.get(bean); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalStateException(e); + } + } + }; + } + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.regex("/manage.*")) + .build(); + } + @Bean public Docket facadesV1Api() { return createDocket() @@ -26,4 +79,5 @@ public class SwaggerConfig { private Docket createDocket() { return new Docket(DocumentationType.SWAGGER_2); } + } \ No newline at end of file diff --git a/airports-service/src/main/java/cz/muni/fi/pa165/airport/observations/health/CustomHealthIndicator.java b/airports-service/src/main/java/cz/muni/fi/pa165/airport/observations/health/CustomHealthIndicator.java new file mode 100644 index 0000000000000000000000000000000000000000..409a131b2e944c7e29aa1abc6943c68347704eb8 --- /dev/null +++ b/airports-service/src/main/java/cz/muni/fi/pa165/airport/observations/health/CustomHealthIndicator.java @@ -0,0 +1,23 @@ +package cz.muni.fi.pa165.airport.observations.health; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +@Component +public class CustomHealthIndicator implements HealthIndicator { + + @Autowired + private HealthProvider healthProvider; + + @Override + public Health health() { + boolean systemHealth = healthProvider.getSystemHealth(); + if (systemHealth) { + return Health.up().build(); + } else { + return Health.down().withDetail("error", "injected failure").build(); + } + } +} diff --git a/airports-service/src/main/java/cz/muni/fi/pa165/airport/observations/health/HealthProvider.java b/airports-service/src/main/java/cz/muni/fi/pa165/airport/observations/health/HealthProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..cdc6f1c793c582ef81c1c9aa723b4f9f7f91a62b --- /dev/null +++ b/airports-service/src/main/java/cz/muni/fi/pa165/airport/observations/health/HealthProvider.java @@ -0,0 +1,15 @@ +package cz.muni.fi.pa165.airport.observations.health; + +import org.springframework.stereotype.Component; + +import java.util.concurrent.atomic.AtomicInteger; + +@Component +public class HealthProvider { + + private AtomicInteger counter = new AtomicInteger(0); + + public boolean getSystemHealth() { + return counter.getAndIncrement() % 2 == 0; + } +} diff --git a/airports-service/src/main/java/cz/muni/fi/pa165/airport/observations/info/CustomInfoContributor.java b/airports-service/src/main/java/cz/muni/fi/pa165/airport/observations/info/CustomInfoContributor.java new file mode 100644 index 0000000000000000000000000000000000000000..da6d0e1e45a6742d3fbdc384a5d901eaf705c109 --- /dev/null +++ b/airports-service/src/main/java/cz/muni/fi/pa165/airport/observations/info/CustomInfoContributor.java @@ -0,0 +1,14 @@ +package cz.muni.fi.pa165.airport.observations.info; + +import org.springframework.boot.actuate.info.Info.Builder; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.stereotype.Component; + +@Component +public class CustomInfoContributor implements InfoContributor { + + @Override + public void contribute(Builder builder) { + builder.withDetail("course", "PA165"); + } +} diff --git a/airports-service/src/main/resources/application.properties b/airports-service/src/main/resources/application.properties index 0e013ec7a6565ee630691c5186914a3d2a978e58..c0ba2135525c49153c3149b5091a899a518c0492 100644 --- a/airports-service/src/main/resources/application.properties +++ b/airports-service/src/main/resources/application.properties @@ -14,4 +14,16 @@ application-description=API Description application-version=1.0 logging.level.org.springframework.web=DEBUG -logging.level.io.springfox=DEBUG \ No newline at end of file +logging.level.io.springfox=DEBUG + +management.endpoints.web.exposure.include=info,health,metrics,loggers,prometheus +management.endpoint.health.show-details=always +management.endpoint.health.probes.enabled=true +management.endpoint.info.enabled=true +management.endpoint.env.enabled=true +management.context-path=/manage +management.endpoints.web.base-path=/manage + +info.app.encoding=UTF-8 +info.app.java.source=17 +info.app.java.target=17 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c0299e606535ca5e82f800a8f93677202763ceac..019d4c99eae0fafba52f305974a52737fc2978e7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -112,3 +112,21 @@ services: image: adminer ports: - '${ADMINER_PORT}:8080' + prometheus: + image: prom/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + volumes: + - './docker/prometheus:/etc/prometheus' + - './docker/prom_data:/prometheus' + ports: + - 8087:9090 +# grafana: +# image: grafana/grafana +# environment: +# - GF_SECURITY_ADMIN_USER=admin +# - GF_SECURITY_ADMIN_PASSWORD=grafana +# volumes: +# - './docker/grafana:/etc/grafana/provisioning/datasources' +# ports: +# - 8088:3000 diff --git a/docker/prometheus/prometheus.yml b/docker/prometheus/prometheus.yml new file mode 100644 index 0000000000000000000000000000000000000000..5ed9404af696a089cd892d367076da8ab89db21a --- /dev/null +++ b/docker/prometheus/prometheus.yml @@ -0,0 +1,24 @@ +global: + scrape_interval: 15s + scrape_timeout: 10s + evaluation_interval: 15s +alerting: + alertmanagers: + - static_configs: + - targets: [] + scheme: http + timeout: 10s + api_version: v1 +scrape_configs: + - job_name: prometheus + honor_timestamps: true + scrape_interval: 15s + scrape_timeout: 10s + metrics_path: /manage/prometheus + scheme: http + static_configs: + - targets: + - hr-service:8080 + - airports-service:8080 + - planes-service:8080 + - flights-service:8080 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 52703f024ffd5b5dc7076594823c56831ec3eacf..e80fd18b3c6c35a490a4dbb4c30adf183fe8a475 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,11 @@ <dependencyManagement> <dependencies> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + <version>1.12.4</version> + </dependency> <dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> @@ -65,6 +70,11 @@ <artifactId>spring-boot-starter-web</artifactId> <version>2.4.3</version> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> + <version>3.2.1</version> + </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId>