diff --git a/pom.xml b/pom.xml index 39edfed..26e8fb9 100644 --- a/pom.xml +++ b/pom.xml @@ -15,12 +15,11 @@ 2.0.12 - 2.15.0 + 0.0.8 UTF-8 UTF-8 21 8022 - wachter 8023 ${server.port} ${management.port} @@ -36,6 +35,11 @@ dev.vality.geck serializer + + dev.vality + woody-http-bridge + ${woody-http-bridge.version} + @@ -83,11 +87,6 @@ - - io.opentelemetry.instrumentation - opentelemetry-spring-boot-starter - ${otel.instrumentation.version} - jakarta.servlet jakarta.servlet-api @@ -112,11 +111,6 @@ spring-boot-starter-test test - - io.opentelemetry - opentelemetry-sdk-testing - test - io.jsonwebtoken jjwt-api diff --git a/src/main/java/dev/vality/wachter/client/ProxyHeadersExtractor.java b/src/main/java/dev/vality/wachter/client/ProxyHeadersExtractor.java index dce2b53..5135111 100644 --- a/src/main/java/dev/vality/wachter/client/ProxyHeadersExtractor.java +++ b/src/main/java/dev/vality/wachter/client/ProxyHeadersExtractor.java @@ -1,6 +1,6 @@ package dev.vality.wachter.client; -import dev.vality.wachter.constants.TraceHeadersConstants; +import dev.vality.woody.http.bridge.tracing.TraceHeadersConstants; import jakarta.servlet.http.HttpServletRequest; import lombok.experimental.UtilityClass; import org.springframework.http.HttpHeaders; diff --git a/src/main/java/dev/vality/wachter/client/WachterClient.java b/src/main/java/dev/vality/wachter/client/WachterClient.java index b2764a1..804bb37 100644 --- a/src/main/java/dev/vality/wachter/client/WachterClient.java +++ b/src/main/java/dev/vality/wachter/client/WachterClient.java @@ -1,12 +1,13 @@ package dev.vality.wachter.client; -import dev.vality.wachter.tracing.TraceContextHeadersExtractor; -import dev.vality.wachter.tracing.TraceContextHeadersNormalizer; +import dev.vality.woody.http.bridge.tracing.TraceContextExtractor; +import dev.vality.woody.http.bridge.tracing.TraceContextHeadersNormalizer; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatusCode; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import org.springframework.web.client.RestClient; @@ -26,7 +27,7 @@ public WachterClientResponse send(HttpServletRequest servletRequest, byte[] cont var httpMethod = resolveMethod(servletRequest); var proxyHeaders = ProxyHeadersExtractor.extractHeaders(servletRequest); - var traceHeaders = TraceContextHeadersExtractor.extractHeaders(); + var traceHeaders = TraceContextExtractor.extractHeaders(); var httpHeaders = new HttpHeaders(); proxyHeaders.forEach(httpHeaders::addAll); @@ -59,4 +60,7 @@ private HttpMethod resolveMethod(HttpServletRequest servletRequest) { return HttpMethod.POST; } } + + public record WachterClientResponse(HttpStatusCode statusCode, HttpHeaders headers, byte[] body) { + } } diff --git a/src/main/java/dev/vality/wachter/client/WachterClientResponse.java b/src/main/java/dev/vality/wachter/client/WachterClientResponse.java deleted file mode 100644 index c5a6f26..0000000 --- a/src/main/java/dev/vality/wachter/client/WachterClientResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.vality.wachter.client; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; - -public record WachterClientResponse(HttpStatusCode statusCode, HttpHeaders headers, byte[] body) { -} diff --git a/src/main/java/dev/vality/wachter/config/OtelConfig.java b/src/main/java/dev/vality/wachter/config/OtelConfig.java deleted file mode 100644 index ee9dd89..0000000 --- a/src/main/java/dev/vality/wachter/config/OtelConfig.java +++ /dev/null @@ -1,70 +0,0 @@ -package dev.vality.wachter.config; - -import dev.vality.wachter.config.properties.OtelConfigProperties; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; -import io.opentelemetry.sdk.trace.samplers.Sampler; -import io.opentelemetry.semconv.ServiceAttributes; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.time.Duration; - -@Slf4j -@Configuration -@ConditionalOnProperty(value = "otel.enabled", havingValue = "true", matchIfMissing = true) -@RequiredArgsConstructor -public class OtelConfig { - - private final OtelConfigProperties otelConfigProperties; - - @Value("${spring.application.name}") - private String applicationName; - - @Bean - public OpenTelemetry openTelemetryConfig() { - var resource = Resource.getDefault() - .merge(Resource.create(Attributes.of(ServiceAttributes.SERVICE_NAME, applicationName))); - var sdkTracerProvider = SdkTracerProvider.builder() - .addSpanProcessor(BatchSpanProcessor.builder(OtlpHttpSpanExporter.builder() - .setEndpoint(otelConfigProperties.getResource()) - .setTimeout(Duration.ofMillis(otelConfigProperties.getTimeout())) - .build()) - .build()) - .setSampler(Sampler.alwaysOn()) - .setResource(resource) - .build(); - var openTelemetrySdk = OpenTelemetrySdk.builder() - .setTracerProvider(sdkTracerProvider) - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .build(); - registerGlobalOpenTelemetry(openTelemetrySdk); - return openTelemetrySdk; - } - - private static void registerGlobalOpenTelemetry(OpenTelemetry openTelemetry) { - try { - GlobalOpenTelemetry.set(openTelemetry); - } catch (Throwable ex) { - log.warn("Please initialize the ObservabilitySdk before starting the application", ex); - GlobalOpenTelemetry.resetForTest(); - try { - GlobalOpenTelemetry.set(openTelemetry); - } catch (Throwable ex1) { - log.warn("Unable to set GlobalOpenTelemetry", ex1); - } - } - } -} diff --git a/src/main/java/dev/vality/wachter/config/WebConfig.java b/src/main/java/dev/vality/wachter/config/WebConfig.java deleted file mode 100644 index 539c1f3..0000000 --- a/src/main/java/dev/vality/wachter/config/WebConfig.java +++ /dev/null @@ -1,73 +0,0 @@ -package dev.vality.wachter.config; - -import dev.vality.wachter.tracing.WoodyTracingFilter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; - -@Configuration -@Slf4j -public class WebConfig { - - @Value("${server.port}") - private int serverPort; - - @Value("/${wachter.endpoint}") - private String wachterEndpoint; - - @Bean - public FilterRegistrationBean externalPortRestrictingFilter() { - var filter = new OncePerRequestFilter() { - @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - var requestPath = getRequestPath(request); - if ((request.getLocalPort() == serverPort) && !requestPath.equals(wachterEndpoint)) { - var status = HttpServletResponse.SC_NOT_FOUND; - log.warn("<- Sent [redirecting {}]: Unknown address {}", status, requestPath); - response.sendError(status, "Unknown address"); - return; - } - filterChain.doFilter(request, response); - } - }; - - var filterRegistrationBean = new FilterRegistrationBean(); - filterRegistrationBean.setFilter(filter); - filterRegistrationBean.setOrder(-100); - filterRegistrationBean.setName("externalPortRestrictingFilter"); - filterRegistrationBean.addUrlPatterns("/*"); - return filterRegistrationBean; - } - - @Bean - public FilterRegistrationBean woodyTracingFilter() { - var registrationBean = new FilterRegistrationBean<>(new WoodyTracingFilter(serverPort, wachterEndpoint)); - registrationBean.setOrder(-50); - registrationBean.setName("woodyTracingFilter"); - registrationBean.addUrlPatterns(wachterEndpoint); - return registrationBean; - } - - public static String getRequestPath(HttpServletRequest request) { - var servletPath = request.getServletPath(); - if (servletPath != null && !servletPath.isBlank()) { - return servletPath; - } - var requestPath = request.getRequestURI(); - if (requestPath != null && !requestPath.isBlank()) { - return requestPath; - } - return ""; - } -} diff --git a/src/main/java/dev/vality/wachter/config/properties/HttpClientProperties.java b/src/main/java/dev/vality/wachter/config/properties/HttpClientProperties.java deleted file mode 100644 index be4695f..0000000 --- a/src/main/java/dev/vality/wachter/config/properties/HttpClientProperties.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.vality.wachter.config.properties; - -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; -import org.springframework.validation.annotation.Validated; - -@Getter -@Setter -@Validated -@Configuration -@ConfigurationProperties("http-client") -public class HttpClientProperties { - - @NotNull - private int maxTotalPooling; - - @NotNull - private int defaultMaxPerRoute; - - @NotNull - private int socketTimeout; - - @NotNull - private int connectionRequestTimeout; - - @NotNull - private int connectTimeout; - -} diff --git a/src/main/java/dev/vality/wachter/config/properties/OtelConfigProperties.java b/src/main/java/dev/vality/wachter/config/properties/OtelConfigProperties.java deleted file mode 100644 index 407339b..0000000 --- a/src/main/java/dev/vality/wachter/config/properties/OtelConfigProperties.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.vality.wachter.config.properties; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -@Getter -@Setter -@Component -@ConfigurationProperties(prefix = "otel") -public class OtelConfigProperties { - - private String resource; - private Long timeout; - -} diff --git a/src/main/java/dev/vality/wachter/config/properties/WachterProperties.java b/src/main/java/dev/vality/wachter/config/properties/WachterProperties.java index 80af146..6aece91 100644 --- a/src/main/java/dev/vality/wachter/config/properties/WachterProperties.java +++ b/src/main/java/dev/vality/wachter/config/properties/WachterProperties.java @@ -27,5 +27,4 @@ public static class Service { private String url; } - } diff --git a/src/main/java/dev/vality/wachter/constants/RequestAttributeNames.java b/src/main/java/dev/vality/wachter/constants/RequestAttributeNames.java deleted file mode 100644 index a8a58eb..0000000 --- a/src/main/java/dev/vality/wachter/constants/RequestAttributeNames.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.vality.wachter.constants; - -public final class RequestAttributeNames { - - public static final String NORMALIZED_WOODY_HEADERS = "wachter.normalizedWoodyHeaders"; - - private RequestAttributeNames() { - } -} diff --git a/src/main/java/dev/vality/wachter/constants/TraceHeadersConstants.java b/src/main/java/dev/vality/wachter/constants/TraceHeadersConstants.java deleted file mode 100644 index 2fc46af..0000000 --- a/src/main/java/dev/vality/wachter/constants/TraceHeadersConstants.java +++ /dev/null @@ -1,73 +0,0 @@ -package dev.vality.wachter.constants; - -import dev.vality.woody.api.trace.context.metadata.user.UserIdentityEmailExtensionKit; -import dev.vality.woody.api.trace.context.metadata.user.UserIdentityIdExtensionKit; -import dev.vality.woody.api.trace.context.metadata.user.UserIdentityRealmExtensionKit; -import dev.vality.woody.api.trace.context.metadata.user.UserIdentityUsernameExtensionKit; -import dev.vality.woody.thrift.impl.http.transport.THttpHeader; - -public class TraceHeadersConstants { - - public static final String WOODY_PREFIX = "woody."; - public static final String WOODY_TRACE_ID = THttpHeader.TRACE_ID.getKey(); - public static final String WOODY_SPAN_ID = THttpHeader.SPAN_ID.getKey(); - public static final String WOODY_PARENT_ID = THttpHeader.PARENT_ID.getKey(); - public static final String WOODY_DEADLINE = THttpHeader.DEADLINE.getKey(); - public static final String WOODY_ERROR_CLASS = THttpHeader.ERROR_CLASS.getKey(); - public static final String WOODY_ERROR_REASON = THttpHeader.ERROR_REASON.getKey(); - public static final String WOODY_META_PREFIX = THttpHeader.META.getKey(); - public static final String WOODY_META_ID = WOODY_META_PREFIX + WoodyMetaHeaders.ID; - public static final String WOODY_META_USERNAME = WOODY_META_PREFIX + WoodyMetaHeaders.USERNAME; - public static final String WOODY_META_EMAIL = WOODY_META_PREFIX + WoodyMetaHeaders.EMAIL; - public static final String WOODY_META_REALM = WOODY_META_PREFIX + WoodyMetaHeaders.REALM; - public static final String WOODY_META_REQUEST_ID = WOODY_META_PREFIX + WoodyMetaHeaders.X_REQUEST_ID; - public static final String WOODY_META_REQUEST_DEADLINE = - WOODY_META_PREFIX + WoodyMetaHeaders.X_REQUEST_DEADLINE; - public static final String WOODY_META_REQUEST_INVOICE_ID = - WOODY_META_PREFIX + WoodyMetaHeaders.X_INVOICE_ID; - - public static final String OTEL_TRACE_PARENT = THttpHeader.TRACE_PARENT.getKey(); - public static final String OTEL_TRACE_STATE = THttpHeader.TRACE_STATE.getKey(); - - public static final class ExternalHeaders { - - public static final String X_REQUEST_ID = "X-Request-ID"; - public static final String X_REQUEST_DEADLINE = "X-Request-Deadline"; - public static final String X_INVOICE_ID = "X-Invoice-ID"; - public static final String X_WOODY_PREFIX = "x-woody-"; - public static final String X_WOODY_TRACE_ID = X_WOODY_PREFIX + "trace-id"; - public static final String X_WOODY_SPAN_ID = X_WOODY_PREFIX + "span-id"; - public static final String X_WOODY_PARENT_ID = X_WOODY_PREFIX + "parent-id"; - public static final String X_WOODY_DEADLINE = X_WOODY_PREFIX + "deadline"; - public static final String X_WOODY_ERROR_CLASS = X_WOODY_PREFIX + "error-class"; - public static final String X_WOODY_ERROR_REASON = X_WOODY_PREFIX + "error-reason"; - public static final String X_WOODY_META_PREFIX = X_WOODY_PREFIX + "meta-"; - public static final String X_WOODY_META_ID = X_WOODY_META_PREFIX + XWoodyMetaHeaders.ID; - public static final String X_WOODY_META_USERNAME = X_WOODY_META_PREFIX + XWoodyMetaHeaders.USERNAME; - public static final String X_WOODY_META_EMAIL = X_WOODY_META_PREFIX + XWoodyMetaHeaders.EMAIL; - public static final String X_WOODY_META_REALM = X_WOODY_META_PREFIX + XWoodyMetaHeaders.REALM; - - public static final class XWoodyMetaHeaders { - - public static final String USER_IDENTITY_PREFIX = "user-identity-"; - public static final String ID = USER_IDENTITY_PREFIX + "id"; - public static final String USERNAME = USER_IDENTITY_PREFIX + "username"; - public static final String EMAIL = USER_IDENTITY_PREFIX + "email"; - public static final String REALM = USER_IDENTITY_PREFIX + "realm"; - - } - } - - public static final class WoodyMetaHeaders { - - public static final String USER_IDENTITY_PREFIX = "user-identity."; - public static final String ID = UserIdentityIdExtensionKit.KEY; - public static final String USERNAME = UserIdentityUsernameExtensionKit.KEY; - public static final String EMAIL = UserIdentityEmailExtensionKit.KEY; - public static final String REALM = UserIdentityRealmExtensionKit.KEY; - public static final String X_REQUEST_ID = USER_IDENTITY_PREFIX + ExternalHeaders.X_REQUEST_ID; - public static final String X_REQUEST_DEADLINE = USER_IDENTITY_PREFIX + ExternalHeaders.X_REQUEST_DEADLINE; - public static final String X_INVOICE_ID = USER_IDENTITY_PREFIX + ExternalHeaders.X_INVOICE_ID; - - } -} diff --git a/src/main/java/dev/vality/wachter/security/JwtTokenDetailsExtractor.java b/src/main/java/dev/vality/wachter/security/JwtTokenDetailsExtractor.java deleted file mode 100644 index 78510d7..0000000 --- a/src/main/java/dev/vality/wachter/security/JwtTokenDetailsExtractor.java +++ /dev/null @@ -1,55 +0,0 @@ -package dev.vality.wachter.security; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimNames; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; - -import java.util.List; -import java.util.Optional; - -public final class JwtTokenDetailsExtractor { - - private static final String PREFERRED_USERNAME = "preferred_username"; - private static final String EMAIL = "email"; - private static final String ISSUER = "iss"; - - private JwtTokenDetailsExtractor() { - } - - public static Optional extractFromContext(Authentication authentication) { - if (!(authentication instanceof JwtAuthenticationToken jwtAuthentication)) { - return Optional.empty(); - } - var token = jwtAuthentication.getToken(); - return Optional.of(new JwtTokenDetails( - token.getClaimAsString(JwtClaimNames.SUB), - token.getClaimAsString(PREFERRED_USERNAME), - token.getClaimAsString(EMAIL), - extractRealm(token), - jwtAuthentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .toList() - )); - } - - private static String extractRealm(Jwt token) { - var issuer = token.getClaimAsString(ISSUER); - if (issuer == null || issuer.isBlank()) { - return null; - } - var lastSlash = issuer.lastIndexOf('/'); - if (lastSlash < 0) { - return issuer; - } - return issuer.substring(lastSlash); - } - - public record JwtTokenDetails(String subject, - String preferredUsername, - String email, - String realm, - List roles) { - } -} diff --git a/src/main/java/dev/vality/wachter/service/WachterService.java b/src/main/java/dev/vality/wachter/service/WachterService.java index 08d78b3..af0d40e 100644 --- a/src/main/java/dev/vality/wachter/service/WachterService.java +++ b/src/main/java/dev/vality/wachter/service/WachterService.java @@ -1,11 +1,10 @@ package dev.vality.wachter.service; import dev.vality.wachter.client.WachterClient; -import dev.vality.wachter.client.WachterClientResponse; import dev.vality.wachter.mapper.ServiceMapper; import dev.vality.wachter.security.AccessData; import dev.vality.wachter.security.AccessService; -import dev.vality.wachter.security.JwtTokenDetailsExtractor; +import dev.vality.woody.http.bridge.util.JwtTokenDetailsExtractor; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; @@ -15,6 +14,8 @@ import java.io.ByteArrayOutputStream; +import static dev.vality.wachter.client.WachterClient.WachterClientResponse; + @RequiredArgsConstructor @Service public class WachterService { diff --git a/src/main/java/dev/vality/wachter/tracing/TraceContextHeadersExtractor.java b/src/main/java/dev/vality/wachter/tracing/TraceContextHeadersExtractor.java deleted file mode 100644 index 70f496b..0000000 --- a/src/main/java/dev/vality/wachter/tracing/TraceContextHeadersExtractor.java +++ /dev/null @@ -1,59 +0,0 @@ -package dev.vality.wachter.tracing; - -import dev.vality.woody.api.trace.context.TraceContext; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.context.propagation.TextMapSetter; -import lombok.experimental.UtilityClass; -import lombok.extern.slf4j.Slf4j; - -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -import static dev.vality.wachter.constants.TraceHeadersConstants.*; - -@Slf4j -@UtilityClass -public class TraceContextHeadersExtractor { - - public Map extractHeaders() { - var traceData = Objects.requireNonNull(TraceContext.getCurrentTraceData(), - "TraceData should be present in TraceContext"); - var otelSpan = Objects.requireNonNull(traceData.getOtelSpan(), - "OTel span should be attached to TraceData"); - if (!otelSpan.getSpanContext().isValid()) { - throw new IllegalStateException("SpanContext must be valid"); - } - - var span = traceData.getActiveSpan().getSpan(); - var headers = new HashMap(); - putIfNotNull(headers, WOODY_TRACE_ID, span.getTraceId()); - putIfNotNull(headers, WOODY_SPAN_ID, span.getId()); - putIfNotNull(headers, WOODY_PARENT_ID, span.getParentId()); - putIfNotNull(headers, WOODY_DEADLINE, - Optional.ofNullable(span.getDeadline()).map(Instant::toString).orElse(null)); - var customMetadata = traceData.getActiveSpan().getCustomMetadata(); - customMetadata.getKeys() - .forEach(s -> putIfNotNull(headers, WOODY_META_PREFIX + s, customMetadata.getValue(s))); - GlobalOpenTelemetry.getPropagators() - .getTextMapPropagator() - .inject(traceData.getOtelContext(), headers, MAP_SETTER); - return headers; - } - - private void putIfNotNull(Map headers, - String key, - String value) { - if (value != null && !value.isEmpty()) { - headers.put(key, value); - } - } - - private static final TextMapSetter> MAP_SETTER = (carrier, key, value) -> { - if (carrier != null && key != null && value != null && !value.isEmpty()) { - carrier.put(key, value); - } - }; -} diff --git a/src/main/java/dev/vality/wachter/tracing/TraceContextHeadersNormalizer.java b/src/main/java/dev/vality/wachter/tracing/TraceContextHeadersNormalizer.java deleted file mode 100644 index 8b2ba3e..0000000 --- a/src/main/java/dev/vality/wachter/tracing/TraceContextHeadersNormalizer.java +++ /dev/null @@ -1,153 +0,0 @@ -package dev.vality.wachter.tracing; - -import dev.vality.wachter.security.JwtTokenDetailsExtractor; -import jakarta.servlet.http.HttpServletRequest; -import lombok.experimental.UtilityClass; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.*; - -import static dev.vality.wachter.constants.TraceHeadersConstants.*; -import static dev.vality.wachter.utils.DeadlineUtil.*; - -@Slf4j -@UtilityClass -public class TraceContextHeadersNormalizer { - - public Map normalize(HttpServletRequest request) { - var normalized = new HashMap(); - normalizeWoodyHeaders(request, normalized); - normalizeOtelHeaders(request, normalized); - mergeJwtIntoHeaders(normalized); - mergeRequestDeadline(request, normalized); - return normalized.isEmpty() ? Map.of() : Map.copyOf(normalized); - } - - public HttpHeaders normalizeResponseHeaders(HttpHeaders responseHeaders) { - var normalized = new HttpHeaders(); - for (var entry : responseHeaders.entrySet()) { - var headerName = entry.getKey(); - var lowerCase = headerName.toLowerCase(Locale.ROOT); - if (lowerCase.startsWith(WOODY_PREFIX)) { - normalizeWoodyResponseHeader(normalized, lowerCase, entry.getValue()); - } else if (lowerCase.equals(OTEL_TRACE_STATE.toLowerCase(Locale.ROOT)) - || lowerCase.equals(OTEL_TRACE_PARENT.toLowerCase(Locale.ROOT))) { - normalized.addAll(headerName, entry.getValue()); - } - } - return normalized; - } - - private void normalizeWoodyHeaders(HttpServletRequest request, Map headers) { - (request.getHeaderNames() != null ? Collections.list(request.getHeaderNames()) : new ArrayList()) - .stream() - .map(s -> s.toLowerCase(Locale.ROOT)) - .filter(s -> s.startsWith(WOODY_PREFIX) || s.startsWith(ExternalHeaders.X_WOODY_PREFIX)) - .forEach(s -> { - if (s.startsWith(ExternalHeaders.X_WOODY_META_PREFIX)) { - var metaKey = s.substring(ExternalHeaders.X_WOODY_META_PREFIX.length()); - if (metaKey.startsWith(ExternalHeaders.XWoodyMetaHeaders.USER_IDENTITY_PREFIX)) { - var userIdentityKey = - metaKey.substring(ExternalHeaders.XWoodyMetaHeaders.USER_IDENTITY_PREFIX.length()); - putIfNotNull(headers, - WOODY_META_PREFIX + WoodyMetaHeaders.USER_IDENTITY_PREFIX + userIdentityKey, - request.getHeader(s)); - } else { - putIfNotNull(headers, WOODY_META_PREFIX + metaKey, request.getHeader(s)); - } - } else if (s.startsWith(ExternalHeaders.X_WOODY_PREFIX)) { - putIfNotNull(headers, WOODY_PREFIX + s.substring(ExternalHeaders.X_WOODY_PREFIX.length()), - request.getHeader(s)); - } else if (s.startsWith(WOODY_PREFIX)) { - putIfNotNull(headers, s, request.getHeader(s)); - } - }); - putIfNotNull(headers, WOODY_META_REQUEST_ID, request.getHeader(ExternalHeaders.X_REQUEST_ID)); - putIfNotNull(headers, WOODY_META_REQUEST_DEADLINE, request.getHeader(ExternalHeaders.X_REQUEST_DEADLINE)); - putIfNotNull(headers, WOODY_META_REQUEST_INVOICE_ID, request.getHeader(ExternalHeaders.X_INVOICE_ID)); - } - - private void normalizeWoodyResponseHeader(HttpHeaders headers, - String lowerCase, - List values) { - if (lowerCase.startsWith(WOODY_META_PREFIX)) { - var metaKey = lowerCase.substring(WOODY_META_PREFIX.length()); - if (metaKey.startsWith(WoodyMetaHeaders.USER_IDENTITY_PREFIX)) { - if (metaKey.equals(WoodyMetaHeaders.X_REQUEST_ID.toLowerCase(Locale.ROOT))) { - headers.addAll(ExternalHeaders.X_REQUEST_ID, values); - } else if (metaKey.equals(WoodyMetaHeaders.X_REQUEST_DEADLINE.toLowerCase(Locale.ROOT))) { - headers.addAll(ExternalHeaders.X_REQUEST_DEADLINE, values); - } else if (metaKey.equals(WoodyMetaHeaders.X_INVOICE_ID.toLowerCase(Locale.ROOT))) { - headers.addAll(ExternalHeaders.X_INVOICE_ID, values); - } else { - var userIdentityKey = metaKey.substring(WoodyMetaHeaders.USER_IDENTITY_PREFIX.length()); - headers.addAll( - ExternalHeaders.X_WOODY_META_PREFIX + - ExternalHeaders.XWoodyMetaHeaders.USER_IDENTITY_PREFIX + - userIdentityKey, values); - } - } else { - headers.addAll(ExternalHeaders.X_WOODY_META_PREFIX + metaKey, values); - } - } else { - headers.addAll(ExternalHeaders.X_WOODY_PREFIX + lowerCase.substring(WOODY_PREFIX.length()), values); - } - } - - private void normalizeOtelHeaders(HttpServletRequest request, Map headers) { - putIfNotNull(headers, OTEL_TRACE_PARENT, request.getHeader(OTEL_TRACE_PARENT)); - putIfNotNull(headers, OTEL_TRACE_STATE, request.getHeader(OTEL_TRACE_STATE)); - } - - private void mergeJwtIntoHeaders(Map headers) { - var tokenDetails = JwtTokenDetailsExtractor.extractFromContext(SecurityContextHolder - .getContext() - .getAuthentication()); - if (tokenDetails.isEmpty()) { - return; - } - var details = tokenDetails.get(); - putIfNotNull(headers, WOODY_META_ID, details.subject()); - putIfNotNull(headers, WOODY_META_USERNAME, details.preferredUsername()); - putIfNotNull(headers, WOODY_META_EMAIL, details.email()); - putIfNotNull(headers, WOODY_META_REALM, details.realm()); - } - - private void mergeRequestDeadline(HttpServletRequest request, Map headers) { - var requestDeadlineHeader = request.getHeader(ExternalHeaders.X_REQUEST_DEADLINE); - var requestIdHeader = request.getHeader(ExternalHeaders.X_REQUEST_ID); - if (requestDeadlineHeader == null) { - return; - } - try { - var normalizedDeadline = getInstant(requestDeadlineHeader, requestIdHeader).toString(); - headers.putIfAbsent(WOODY_DEADLINE, normalizedDeadline); - headers.put(WOODY_META_REQUEST_DEADLINE, normalizedDeadline); - } catch (Exception e) { - log.warn("Unable to parse '" + ExternalHeaders.X_REQUEST_DEADLINE + "' header value '{}'", - requestDeadlineHeader); - } - } - - private void putIfNotNull(Map headers, - String key, - String value) { - if (value != null && !value.isEmpty()) { - headers.put(key, value); - } - } - - private Instant getInstant(String requestDeadlineHeader, String requestIdHeader) { - if (containsRelativeValues(requestDeadlineHeader, requestIdHeader)) { - return Instant.now() - .plus(extractMilliseconds(requestDeadlineHeader, requestIdHeader), ChronoUnit.MILLIS) - .plus(extractSeconds(requestDeadlineHeader, requestIdHeader), ChronoUnit.MILLIS) - .plus(extractMinutes(requestDeadlineHeader, requestIdHeader), ChronoUnit.MILLIS); - } - return Instant.parse(requestDeadlineHeader); - } -} diff --git a/src/main/java/dev/vality/wachter/tracing/TraceContextHeadersValidation.java b/src/main/java/dev/vality/wachter/tracing/TraceContextHeadersValidation.java deleted file mode 100644 index 1c98bba..0000000 --- a/src/main/java/dev/vality/wachter/tracing/TraceContextHeadersValidation.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.vality.wachter.tracing; - -import lombok.experimental.UtilityClass; -import lombok.extern.slf4j.Slf4j; - -import java.util.LinkedHashMap; -import java.util.Map; - -import static dev.vality.wachter.constants.TraceHeadersConstants.*; - -@Slf4j -@UtilityClass -public class TraceContextHeadersValidation { - - public LinkedHashMap validate(Map normalized) { - var copy = new LinkedHashMap<>(normalized); - var traceId = copy.get(WOODY_TRACE_ID); - if (traceId != null && traceId.equals(copy.get(WOODY_SPAN_ID))) { - copy.remove(WOODY_SPAN_ID); - } - if ("undefined".equals(copy.get(WOODY_PARENT_ID))) { - copy.remove(WOODY_PARENT_ID); - } - return copy; - } -} diff --git a/src/main/java/dev/vality/wachter/tracing/TraceContextRestorer.java b/src/main/java/dev/vality/wachter/tracing/TraceContextRestorer.java deleted file mode 100644 index dd8faac..0000000 --- a/src/main/java/dev/vality/wachter/tracing/TraceContextRestorer.java +++ /dev/null @@ -1,76 +0,0 @@ -package dev.vality.wachter.tracing; - -import dev.vality.woody.api.flow.WFlow; -import dev.vality.woody.api.trace.TraceData; -import dev.vality.woody.api.trace.context.TraceContext; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.TextMapGetter; -import lombok.experimental.UtilityClass; -import lombok.extern.slf4j.Slf4j; - -import java.time.Instant; -import java.util.Map; -import java.util.function.Consumer; - -import static dev.vality.wachter.constants.TraceHeadersConstants.*; - -@Slf4j -@UtilityClass -public class TraceContextRestorer { - - public TraceData restoreTraceData(Map headers) { - var traceData = TraceContext.initNewServiceTrace(new TraceData(), - WFlow.createDefaultIdGenerator(), WFlow.createDefaultIdGenerator()); - if (headers.isEmpty()) { - return traceData; - } - var span = traceData.getActiveSpan().getSpan(); - setIfPresent(headers, WOODY_TRACE_ID, span::setTraceId); - setIfPresent(headers, WOODY_SPAN_ID, span::setId); - setIfPresent(headers, WOODY_PARENT_ID, span::setParentId); - setIfPresent(headers, WOODY_DEADLINE, value -> span.setDeadline(Instant.parse(value))); - span.setTimestamp(0); - span.setDuration(0); - var customMetadata = traceData.getActiveSpan().getCustomMetadata(); - headers.keySet() - .stream() - .filter(s -> s.startsWith(WOODY_META_PREFIX)) - .forEach(s -> { - var metaKey = s.substring(WOODY_META_PREFIX.length()); - setIfPresent(headers, s, value -> customMetadata.putValue(metaKey, value)); - }); - var extracted = GlobalOpenTelemetry.getPropagators() - .getTextMapPropagator() - .extract(Context.root(), headers, HEADER_GETTER); - if (io.opentelemetry.api.trace.Span.fromContext(extracted).getSpanContext().isValid()) { - traceData.setPendingParentContext(extracted); - traceData.setInboundTraceParent(headers.get(OTEL_TRACE_PARENT)); - traceData.setInboundTraceState(headers.getOrDefault(OTEL_TRACE_STATE, null)); - } - return traceData; - } - - private void setIfPresent(Map headers, String key, Consumer consumer) { - var value = headers.get(key); - if (value != null && !value.isEmpty()) { - try { - consumer.accept(value); - } catch (Exception e) { - log.warn("Unable to set header with key '{}' value '{}'", key, value); - } - } - } - - private static final TextMapGetter> HEADER_GETTER = new TextMapGetter<>() { - @Override - public Iterable keys(Map carrier) { - return carrier.keySet(); - } - - @Override - public String get(Map carrier, String key) { - return carrier.get(key); - } - }; -} diff --git a/src/main/java/dev/vality/wachter/tracing/WoodyTracingFilter.java b/src/main/java/dev/vality/wachter/tracing/WoodyTracingFilter.java deleted file mode 100644 index 1fb1143..0000000 --- a/src/main/java/dev/vality/wachter/tracing/WoodyTracingFilter.java +++ /dev/null @@ -1,162 +0,0 @@ -package dev.vality.wachter.tracing; - -import dev.vality.woody.api.flow.WFlow; -import dev.vality.woody.api.trace.context.TraceContext; -import io.opentelemetry.semconv.HttpAttributes; -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.slf4j.MDC; -import org.springframework.http.HttpHeaders; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import static dev.vality.wachter.config.WebConfig.getRequestPath; -import static dev.vality.wachter.constants.TraceHeadersConstants.WOODY_TRACE_ID; -import static io.opentelemetry.api.trace.StatusCode.ERROR; -import static io.opentelemetry.api.trace.StatusCode.OK; - -@Slf4j -@RequiredArgsConstructor -public final class WoodyTracingFilter extends OncePerRequestFilter { - - private static final Set SENSITIVE_HEADERS = Set.of( - HttpHeaders.AUTHORIZATION.toLowerCase(Locale.ROOT), - HttpHeaders.COOKIE.toLowerCase(Locale.ROOT), - HttpHeaders.SET_COOKIE.toLowerCase(Locale.ROOT) - ); - - private final int serverPort; - private final String wachterEndpoint; - - @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) { - var requestPath = getRequestPath(request); - if ((request.getLocalPort() == serverPort) && requestPath.equals(wachterEndpoint)) { - var normalized = TraceContextHeadersNormalizer.normalize(request); - var validated = TraceContextHeadersValidation.validate(normalized); - MDC.put(WOODY_TRACE_ID, validated.get(WOODY_TRACE_ID) != null ? validated.get(WOODY_TRACE_ID) : ""); - log.info("-> Received {} {} | params: {}, headers: {}", request.getMethod(), getRequestPath(request), - extractParams(request), sanitizeHeaders(request)); - MDC.remove(WOODY_TRACE_ID); - var restoredTraceData = TraceContextRestorer.restoreTraceData(validated); - - WFlow.create(() -> doFilterWithTraceHandling(request, response, filterChain), restoredTraceData).run(); - - MDC.put(WOODY_TRACE_ID, validated.get(WOODY_TRACE_ID) != null ? validated.get(WOODY_TRACE_ID) : ""); - log.info("<- Sent {} {} | status: {}, headers: {}", request.getMethod(), getRequestPath(request), - response.getStatus(), sanitizeResponseHeaders(response)); - MDC.remove(WOODY_TRACE_ID); - return; - } - doFilter(request, response, filterChain); - } - - @SneakyThrows - private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { - filterChain.doFilter(request, response); - } - - @SneakyThrows - private void doFilterWithTraceHandling(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) { - var traceData = TraceContext.getCurrentTraceData(); - var span = traceData != null ? traceData.getOtelSpan() : null; - try { - filterChain.doFilter(request, response); - recordResponse(span, response); - } catch (Throwable t) { - recordException(span, response, t); - throw t; - } - } - - private void recordResponse(io.opentelemetry.api.trace.Span span, HttpServletResponse response) { - if (span == null || !span.getSpanContext().isValid()) { - return; - } - var status = response.getStatus(); - if (status > 0) { - span.setAttribute(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, status); - span.setStatus(status >= 500 ? ERROR : OK); - } else { - span.setStatus(OK); - } - } - - private void recordException(io.opentelemetry.api.trace.Span span, - HttpServletResponse response, - Throwable throwable) { - if (span == null || !span.getSpanContext().isValid()) { - return; - } - var status = response.getStatus(); - if (status > 0) { - span.setAttribute(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, status); - } - span.recordException(throwable); - span.setStatus(ERROR); - } - - - public static String extractParams(HttpServletRequest servletRequest) { - return servletRequest.getParameterMap().entrySet().stream() - .map(entry -> entry.getKey() + "=" + String.join(",", entry.getValue())) - .collect(Collectors.joining(", ")); - } - - private static HttpHeaders sanitizeHeaders(HttpServletRequest request) { - var headers = new HttpHeaders(); - var collectedHeaders = collectHeaders(request); - collectedHeaders.forEach((name, value) -> { - if (isSensitive(name)) { - headers.add(name, "***"); - } else { - headers.add(name, value); - } - }); - return headers; - } - - private static Map collectHeaders(HttpServletRequest request) { - var headers = new LinkedHashMap(); - var headerNames = request.getHeaderNames(); - if (headerNames != null) { - while (headerNames.hasMoreElements()) { - var name = headerNames.nextElement(); - var value = request.getHeader(name); - if (value != null) { - headers.put(name, value); - } - } - } - return headers; - } - - private static boolean isSensitive(String headerName) { - return SENSITIVE_HEADERS.contains(headerName.toLowerCase(Locale.ROOT)); - } - - private static HttpHeaders sanitizeResponseHeaders(HttpServletResponse response) { - var headers = new HttpHeaders(); - response.getHeaderNames().forEach(name -> { - if (isSensitive(name)) { - headers.add(name, "***"); - } else { - response.getHeaders(name).forEach(value -> headers.add(name, value)); - } - }); - return headers; - } -} diff --git a/src/main/java/dev/vality/wachter/utils/DeadlineUtil.java b/src/main/java/dev/vality/wachter/utils/DeadlineUtil.java deleted file mode 100644 index 8606e50..0000000 --- a/src/main/java/dev/vality/wachter/utils/DeadlineUtil.java +++ /dev/null @@ -1,110 +0,0 @@ -package dev.vality.wachter.utils; - -import dev.vality.wachter.exceptions.DeadlineException; -import jakarta.annotation.Nullable; -import lombok.experimental.UtilityClass; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.regex.Pattern; - -@UtilityClass -@SuppressWarnings("ParameterName") -public class DeadlineUtil { - - public static void checkDeadline(@Nullable String xRequestDeadline, String xRequestId) { - if (xRequestDeadline == null) { - return; - } - if (containsRelativeValues(xRequestDeadline, xRequestId)) { - return; - } - try { - Instant instant = Instant.parse(xRequestDeadline); - if (Instant.now().isAfter(instant)) { - throw new DeadlineException(String.format("Deadline has expired, xRequestId=%s ", xRequestId)); - } - } catch (Exception e) { - throw new DeadlineException( - String.format("Deadline has invalid 'Instant' format, xRequestId=%s ", xRequestId)); - } - } - - public static boolean containsRelativeValues(String xRequestDeadline, String xRequestId) { - return (extractMinutes(xRequestDeadline, xRequestId) + extractSeconds(xRequestDeadline, xRequestId) + - extractMilliseconds(xRequestDeadline, xRequestId)) > 0; - } - - public static Long extractMinutes(String xRequestDeadline, String xRequestId) { - var format = "minutes"; - - checkNegativeValues(xRequestDeadline, xRequestId, "([-][0-9]+([.][0-9]+)?(?!ms)[m])", format); - - var minutes = extractValue(xRequestDeadline, "([0-9]+([.][0-9]+)?(?!ms)[m])", xRequestId, format); - - return Optional.ofNullable(minutes).map(min -> min * 60000.0).map(Double::longValue).orElse(0L); - } - - public static Long extractSeconds(String xRequestDeadline, String xRequestId) { - var format = "seconds"; - - checkNegativeValues(xRequestDeadline, xRequestId, "([-][0-9]+([.][0-9]+)?[s])", format); - - var seconds = extractValue(xRequestDeadline, "([0-9]+([.][0-9]+)?[s])", xRequestId, format); - - return Optional.ofNullable(seconds).map(s -> s * 1000.0).map(Double::longValue).orElse(0L); - } - - public static Long extractMilliseconds(String xRequestDeadline, String xRequestId) { - var format = "milliseconds"; - - checkNegativeValues(xRequestDeadline, xRequestId, "([-][0-9]+([.][0-9]+)?[m][s])", format); - - var milliseconds = extractValue(xRequestDeadline, "([0-9]+([.][0-9]+)?[m][s])", xRequestId, format); - - if (milliseconds != null && Math.ceil(milliseconds % 1) > 0) { - throw new DeadlineException( - String.format("Deadline 'milliseconds' parameter can have only integer value, xRequestId=%s ", - xRequestId)); - } - - return Optional.ofNullable(milliseconds).map(Double::longValue).orElse(0L); - } - - private static void checkNegativeValues(String xRequestDeadline, String xRequestId, String regex, String format) { - if (!match(regex, xRequestDeadline).isEmpty()) { - throw new DeadlineException( - String.format("Deadline '%s' parameter has negative value, xRequestId=%s ", format, xRequestId)); - } - } - - private static Double extractValue(String xRequestDeadline, String formatRegex, String xRequestId, String format) { - var numberRegex = "([0-9]+([.][0-9]+)?)"; - - var doubles = new ArrayList(); - for (String string : match(formatRegex, xRequestDeadline)) { - doubles.addAll(match(numberRegex, string)); - } - if (doubles.size() > 1) { - throw new DeadlineException( - String.format("Deadline '%s' parameter has a few relative value, xRequestId=%s ", format, - xRequestId)); - } - if (doubles.isEmpty()) { - return null; - } - return Double.valueOf(doubles.getFirst()); - } - - private static List match(String regex, String data) { - var pattern = Pattern.compile(regex); - var matcher = pattern.matcher(data); - var strings = new ArrayList(); - while (matcher.find()) { - strings.add(matcher.group()); - } - return strings; - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9059e12..de40fc7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,7 +7,7 @@ management: port: ${management.port} metrics: tags: - application: '@project.name@' + application: ${project.name} endpoint: health: show-details: always @@ -26,7 +26,7 @@ management: spring: application: - name: '@project.name@' + name: ${project.name} output: ansi: enabled: always @@ -39,12 +39,8 @@ spring: issuer-uri: > ${spring.security.oauth2.resourceserver.url}/auth/realms/ ${spring.security.oauth2.resourceserver.jwt.realm} -info: - version: '@project.version@' - stage: dev wachter: - endpoint: ${wachter.endpoint} auth: enabled: true serviceHeader: Service @@ -128,20 +124,21 @@ wachter: name: DMTClient url: http://dmt.default:8022/v1/domain/repository_client +auth: + enabled: true -http-client: - connectTimeout: 10000 - connectionRequestTimeout: 10000 - socketTimeout: 10000 - maxTotalPooling: 200 - defaultMaxPerRoute: 200 - -auth.enabled: true +woody-http-bridge: + tracing: + endpoints: + - path: /wachter + port: ${server.port} + request-header-mode: WOODY_OR_X_WOODY + response-header-mode: OFF otel: + enabled: true resource: http://localhost:4318/v1/traces timeout: 60000 - enabled: true http: requestTimeout: 60000 diff --git a/src/test/java/dev/vality/wachter/client/WachterClientOperationsTest.java b/src/test/java/dev/vality/wachter/client/WachterClientOperationsTest.java index 9327a72..0c856e3 100644 --- a/src/test/java/dev/vality/wachter/client/WachterClientOperationsTest.java +++ b/src/test/java/dev/vality/wachter/client/WachterClientOperationsTest.java @@ -3,8 +3,9 @@ import dev.vality.woody.api.trace.TraceData; import dev.vality.woody.api.trace.context.TraceContext; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.trace.SdkTracerProvider; @@ -19,7 +20,7 @@ import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestClient; -import static dev.vality.wachter.constants.TraceHeadersConstants.ExternalHeaders.X_WOODY_TRACE_ID; +import static dev.vality.woody.http.bridge.tracing.TraceHeadersConstants.ExternalHeaders.X_WOODY_TRACE_ID; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; @@ -29,7 +30,6 @@ class WachterClientOperationsTest { private SdkTracerProvider tracerProvider; - private Tracer tracer; @BeforeEach void setUp() { @@ -40,7 +40,6 @@ void setUp() { .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .build(); GlobalOpenTelemetry.set(openTelemetry); - tracer = openTelemetry.getTracer("test"); } @AfterEach @@ -58,10 +57,7 @@ void shouldSendRequestWithTracingHeaders() { final var server = MockRestServiceServer.bindTo(builder).build(); final var restClient = builder.build(); - final var traceData = new TraceData(); - final var otelSpan = tracer.spanBuilder("test-span").startSpan(); - traceData.setOtelSpan(otelSpan); - TraceContext.setCurrentTraceData(traceData); + final var traceData = prepareTraceData("test-span"); final var serviceSpan = traceData.getServiceSpan().getSpan(); serviceSpan.setTraceId("test-trace-id"); @@ -91,7 +87,7 @@ void shouldSendRequestWithTracingHeaders() { assertEquals(HttpStatus.OK, actualResponse.statusCode()); assertArrayEquals(expectedResponse, actualResponse.body()); server.verify(); - otelSpan.end(); + traceData.finishOtelSpan(); } @Test @@ -100,10 +96,7 @@ void shouldFilterDisallowedHeaders() { final var server = MockRestServiceServer.bindTo(builder).build(); final var restClient = builder.build(); - final var traceData = new TraceData(); - final var otelSpan = tracer.spanBuilder("test-span").startSpan(); - traceData.setOtelSpan(otelSpan); - TraceContext.setCurrentTraceData(traceData); + final var traceData = prepareTraceData("test-span"); traceData.getServiceSpan().getSpan().setTraceId("filter-trace-id"); traceData.getServiceSpan().getSpan().setId("filter-span-id"); @@ -127,7 +120,7 @@ void shouldFilterDisallowedHeaders() { client.send(servletRequest, null, "http://upstream/disallowed"); server.verify(); - otelSpan.end(); + traceData.finishOtelSpan(); } @Test @@ -136,10 +129,7 @@ void shouldHandleGetRequestWithoutBody() { final var server = MockRestServiceServer.bindTo(builder).build(); final var restClient = builder.build(); - final var traceData = new TraceData(); - final var otelSpan = tracer.spanBuilder("test-span").startSpan(); - traceData.setOtelSpan(otelSpan); - TraceContext.setCurrentTraceData(traceData); + final var traceData = prepareTraceData("test-span"); final var serviceSpan = traceData.getServiceSpan().getSpan(); serviceSpan.setTraceId("get-trace-id"); @@ -158,7 +148,7 @@ void shouldHandleGetRequestWithoutBody() { assertEquals(HttpStatus.OK, response.statusCode()); assertArrayEquals("{}".getBytes(), response.body()); server.verify(); - otelSpan.end(); + traceData.finishOtelSpan(); } @Test @@ -167,10 +157,7 @@ void shouldReturnErrorResponseWithoutThrowing() { final var server = MockRestServiceServer.bindTo(builder).build(); final var restClient = builder.build(); - final var traceData = new TraceData(); - final var otelSpan = tracer.spanBuilder("test-span").startSpan(); - traceData.setOtelSpan(otelSpan); - TraceContext.setCurrentTraceData(traceData); + final var traceData = prepareTraceData("test-span"); final var serviceSpan = traceData.getServiceSpan().getSpan(); serviceSpan.setTraceId("error-trace-id"); @@ -192,6 +179,14 @@ void shouldReturnErrorResponseWithoutThrowing() { assertEquals(HttpStatus.BAD_GATEWAY, response.statusCode()); assertArrayEquals("bad-gateway".getBytes(), response.body()); server.verify(); - otelSpan.end(); + traceData.finishOtelSpan(); + } + + private TraceData prepareTraceData(String spanName) { + final var traceData = new TraceData(); + traceData.startNewOtelSpan(spanName, SpanKind.SERVER, Context.current()); + traceData.openOtelScope(); + TraceContext.setCurrentTraceData(traceData); + return traceData; } } diff --git a/src/test/java/dev/vality/wachter/config/AbstractKeycloakOpenIdAsWiremockConfig.java b/src/test/java/dev/vality/wachter/config/AbstractKeycloakOpenIdAsWiremockConfig.java index 05ec03a..1e2970c 100644 --- a/src/test/java/dev/vality/wachter/config/AbstractKeycloakOpenIdAsWiremockConfig.java +++ b/src/test/java/dev/vality/wachter/config/AbstractKeycloakOpenIdAsWiremockConfig.java @@ -21,7 +21,12 @@ "server.port=8083", "spring.security.oauth2.resourceserver.url=${wiremock.server.baseUrl}", "spring.security.oauth2.resourceserver.jwt.issuer-uri=${wiremock.server.baseUrl}/auth/realms/" + - "${spring.security.oauth2.resourceserver.jwt.realm}"}) + "${spring.security.oauth2.resourceserver.jwt.realm}", + "woody-http-bridge.tracing.endpoints[0].path=/wachter", + "woody-http-bridge.tracing.endpoints[0].port=8083", + "woody-http-bridge.tracing.endpoints[0].request-header-mode: WOODY_OR_X_WOODY", + "woody-http-bridge.tracing.endpoints[0].response-header-mode: OFF", + }) @AutoConfigureMockMvc @EnableWireMock @ExtendWith(SpringExtension.class) diff --git a/src/test/java/dev/vality/wachter/controller/WachterControllerDisabledAuthTest.java b/src/test/java/dev/vality/wachter/controller/WachterControllerDisabledAuthTest.java index 3cf3139..ddc302b 100644 --- a/src/test/java/dev/vality/wachter/controller/WachterControllerDisabledAuthTest.java +++ b/src/test/java/dev/vality/wachter/controller/WachterControllerDisabledAuthTest.java @@ -1,7 +1,6 @@ package dev.vality.wachter.controller; import dev.vality.wachter.client.WachterClient; -import dev.vality.wachter.client.WachterClientResponse; import dev.vality.wachter.config.AbstractKeycloakOpenIdAsWiremockConfig; import dev.vality.wachter.testutil.TMessageUtil; import lombok.SneakyThrows; @@ -20,6 +19,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; +import static dev.vality.wachter.client.WachterClient.WachterClientResponse; import static java.util.UUID.randomUUID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; diff --git a/src/test/java/dev/vality/wachter/controller/WachterControllerTest.java b/src/test/java/dev/vality/wachter/controller/WachterControllerTest.java index d405d47..07ae6da 100644 --- a/src/test/java/dev/vality/wachter/controller/WachterControllerTest.java +++ b/src/test/java/dev/vality/wachter/controller/WachterControllerTest.java @@ -1,7 +1,6 @@ package dev.vality.wachter.controller; import dev.vality.wachter.client.WachterClient; -import dev.vality.wachter.client.WachterClientResponse; import dev.vality.wachter.config.AbstractKeycloakOpenIdAsWiremockConfig; import dev.vality.wachter.testutil.TMessageUtil; import lombok.SneakyThrows; @@ -19,7 +18,8 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; -import static dev.vality.wachter.constants.TraceHeadersConstants.*; +import static dev.vality.wachter.client.WachterClient.WachterClientResponse; +import static dev.vality.woody.http.bridge.tracing.TraceHeadersConstants.*; import static java.util.UUID.randomUUID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; diff --git a/src/test/java/dev/vality/wachter/integration/WachterIntegrationTest.java b/src/test/java/dev/vality/wachter/integration/WachterIntegrationTest.java index 383ed47..a2d12d0 100644 --- a/src/test/java/dev/vality/wachter/integration/WachterIntegrationTest.java +++ b/src/test/java/dev/vality/wachter/integration/WachterIntegrationTest.java @@ -28,7 +28,7 @@ import java.util.UUID; import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static dev.vality.wachter.constants.TraceHeadersConstants.*; +import static dev.vality.woody.http.bridge.tracing.TraceHeadersConstants.*; import static org.junit.jupiter.api.Assertions.*; @TestPropertySource(properties = { diff --git a/src/test/java/dev/vality/wachter/testutil/ContextUtil.java b/src/test/java/dev/vality/wachter/testutil/ContextUtil.java deleted file mode 100644 index 34be37f..0000000 --- a/src/test/java/dev/vality/wachter/testutil/ContextUtil.java +++ /dev/null @@ -1,33 +0,0 @@ -package dev.vality.wachter.testutil; - -import dev.vality.geck.serializer.kit.mock.FieldHandler; -import dev.vality.geck.serializer.kit.mock.MockMode; -import dev.vality.geck.serializer.kit.mock.MockTBaseProcessor; -import dev.vality.geck.serializer.kit.tbase.TBaseHandler; -import lombok.SneakyThrows; -import lombok.experimental.UtilityClass; -import org.apache.thrift.TBase; - -import java.time.Instant; -import java.util.Map; - -@UtilityClass -public class ContextUtil { - - private static final MockTBaseProcessor mockRequiredTBaseProcessor; - - static { - mockRequiredTBaseProcessor = new MockTBaseProcessor(MockMode.REQUIRED_ONLY, 15, 1); - Map.Entry timeFields = Map.entry( - structHandler -> structHandler.value(Instant.now().toString()), - new String[] {"conversation_id", "messages", "status", "user_id", "email", "fullname", - "held_until", "from_time", "to_time"} - ); - mockRequiredTBaseProcessor.addFieldHandler(timeFields.getKey(), timeFields.getValue()); - } - - @SneakyThrows - public static T fillRequiredTBaseObject(T tbase, Class type) { - return ContextUtil.mockRequiredTBaseProcessor.process(tbase, new TBaseHandler<>(type)); - } -} diff --git a/src/test/java/dev/vality/wachter/tracing/ServletInstrumentationTest.java b/src/test/java/dev/vality/wachter/tracing/ServletInstrumentationTest.java deleted file mode 100644 index a7fd331..0000000 --- a/src/test/java/dev/vality/wachter/tracing/ServletInstrumentationTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package dev.vality.wachter.tracing; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.data.SpanData; -import org.junit.jupiter.api.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.TestPropertySource; -import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.client.RestClient; - -import java.time.Duration; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@TestPropertySource(properties = { - "otel.enabled=false", - "auth.enabled=false" -}) -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@Import(ServletInstrumentationTestConfig.class) -class ServletInstrumentationTest { - - private static final AttributeKey HTTP_METHOD = AttributeKey.stringKey("http.request.method"); - private static final AttributeKey HTTP_STATUS_LONG = AttributeKey.longKey("http.response.status_code"); - - @Value("${local.server.port}") - private int port; - - @Autowired - private RestClient restClient; - - @Autowired - private InMemorySpanExporter spanExporter; - - @Autowired - private SdkTracerProvider tracerProvider; - - @BeforeEach - void setUp() { - spanExporter.reset(); - } - - @AfterEach - void tearDown() { - spanExporter.reset(); - } - - @AfterAll - void shutdownTelemetry() { - tracerProvider.close(); - spanExporter.shutdown(); - GlobalOpenTelemetry.resetForTest(); - } - - @Test - void shouldCaptureServletSpanWithHttpAttributes() throws InterruptedException { - int statusCode; - try { - var response = restClient.get() - .uri("http://localhost:" + port + "/test/ping") - .retrieve() - .toEntity(String.class); - statusCode = response.getStatusCode().value(); - } catch (HttpStatusCodeException ex) { - statusCode = ex.getStatusCode().value(); - } - - assertTrue(statusCode > 0); - - List spans = waitForSpans(); - SpanData serverSpan = spans.stream() - .filter(span -> span.getKind() == SpanKind.SERVER) - .findFirst() - .orElseThrow(() -> new AssertionError("Expected SERVER span")); - - assertEquals("GET", serverSpan.getAttributes().get(HTTP_METHOD)); - int expectedStatus = statusCode; - Long statusLong = serverSpan.getAttributes().get(HTTP_STATUS_LONG); - if (statusLong != null) { - assertEquals(expectedStatus, statusLong.intValue()); - } else { - assertEquals(expectedStatus, serverSpan.getAttributes().get(HTTP_STATUS_LONG)); - } - assertTrue(serverSpan.getName().contains("/test/ping")); - - SpanData clientSpan = spans.stream() - .filter(span -> span.getKind() == SpanKind.CLIENT) - .findFirst() - .orElseThrow(() -> new AssertionError("Expected CLIENT span")); - - assertEquals("GET", clientSpan.getAttributes().get(HTTP_METHOD)); - Long clientStatusLong = clientSpan.getAttributes().get(HTTP_STATUS_LONG); - if (clientStatusLong != null) { - assertEquals(expectedStatus, clientStatusLong.intValue()); - } else { - assertEquals(expectedStatus, clientSpan.getAttributes().get(HTTP_STATUS_LONG)); - } - } - - private List waitForSpans() throws InterruptedException { - long deadline = System.nanoTime() + Duration.ofSeconds(2).toNanos(); - while ((spanExporter.getFinishedSpanItems().size() < 2) && System.nanoTime() < deadline) { - Thread.sleep(25); - } - List spans = spanExporter.getFinishedSpanItems(); - if (spans.size() < 2) { - fail("Expected client and server spans to be exported"); - } - return spans; - } -} diff --git a/src/test/java/dev/vality/wachter/tracing/ServletInstrumentationTestConfig.java b/src/test/java/dev/vality/wachter/tracing/ServletInstrumentationTestConfig.java deleted file mode 100644 index f72ad6b..0000000 --- a/src/test/java/dev/vality/wachter/tracing/ServletInstrumentationTestConfig.java +++ /dev/null @@ -1,57 +0,0 @@ -package dev.vality.wachter.tracing; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@TestConfiguration(proxyBeanMethods = false) -class ServletInstrumentationTestConfig { - - @Bean - @Primary - InMemorySpanExporter inMemorySpanExporter() { - return InMemorySpanExporter.create(); - } - - @Bean - @Primary - SdkTracerProvider sdkTracerProvider(InMemorySpanExporter exporter) { - return SdkTracerProvider.builder() - .addSpanProcessor(SimpleSpanProcessor.create(exporter)) - .setResource(Resource.create(Attributes.of(AttributeKey.stringKey("service.name"), "wachter-test"))) - .build(); - } - - @Bean - @Primary - OpenTelemetrySdk openTelemetrySdk(SdkTracerProvider tracerProvider) { - GlobalOpenTelemetry.resetForTest(); - OpenTelemetrySdk sdk = OpenTelemetrySdk.builder() - .setTracerProvider(tracerProvider) - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .build(); - GlobalOpenTelemetry.set(sdk); - return sdk; - } - - @RestController - static class TestController { - - @GetMapping("/test/ping") - String ping() { - return "pong"; - } - } -} diff --git a/src/test/java/dev/vality/wachter/tracing/TraceContextHeadersExtractorTest.java b/src/test/java/dev/vality/wachter/tracing/TraceContextHeadersExtractorTest.java deleted file mode 100644 index 313ab64..0000000 --- a/src/test/java/dev/vality/wachter/tracing/TraceContextHeadersExtractorTest.java +++ /dev/null @@ -1,273 +0,0 @@ -package dev.vality.wachter.tracing; - -import dev.vality.woody.api.flow.WFlow; -import dev.vality.woody.api.trace.TraceData; -import dev.vality.woody.api.trace.context.TraceContext; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import static dev.vality.wachter.constants.TraceHeadersConstants.*; -import static org.junit.jupiter.api.Assertions.*; - -class TraceContextHeadersExtractorTest { - - private SdkTracerProvider tracerProvider; - private Tracer tracer; - - @BeforeEach - void setUp() { - GlobalOpenTelemetry.resetForTest(); - tracerProvider = SdkTracerProvider.builder().build(); - final var openTelemetry = OpenTelemetrySdk.builder() - .setTracerProvider(tracerProvider) - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .build(); - GlobalOpenTelemetry.set(openTelemetry); - tracer = openTelemetry.getTracer("test"); - } - - @AfterEach - void tearDown() { - TraceContext.setCurrentTraceData(null); - GlobalOpenTelemetry.resetForTest(); - if (tracerProvider != null) { - tracerProvider.close(); - } - } - - @Test - void shouldExtractWoodyHeadersFromTraceContext() { - final var traceData = new TraceData(); - final var otelSpan = tracer.spanBuilder("test-span").startSpan(); - traceData.setOtelSpan(otelSpan); - TraceContext.setCurrentTraceData(traceData); - - final var activeSpan = traceData.getActiveSpan(); - final var span = activeSpan.getSpan(); - span.setTraceId("trace-id"); - span.setId("span-id"); - span.setParentId("parent-id"); - span.setDeadline(Instant.parse("2030-01-01T00:00:00Z")); - activeSpan.getCustomMetadata().putValue(WoodyMetaHeaders.ID, "user-id"); - activeSpan.getCustomMetadata().putValue(WoodyMetaHeaders.USERNAME, "username"); - activeSpan.getCustomMetadata().putValue(WoodyMetaHeaders.EMAIL, "user@example.com"); - activeSpan.getCustomMetadata().putValue(WoodyMetaHeaders.REALM, "/realm"); - - final Map headers = TraceContextHeadersExtractor.extractHeaders(); - - assertNotNull(headers); - assertTrue(headers.containsKey(WOODY_TRACE_ID)); - assertTrue(headers.containsKey(WOODY_SPAN_ID)); - assertTrue(headers.containsKey(OTEL_TRACE_PARENT)); - - otelSpan.end(); - } - - @Test - void shouldExtractOnlyAvailableHeaders() { - final var traceData = new TraceData(); - final var otelSpan = tracer.spanBuilder("test-span").startSpan(); - traceData.setOtelSpan(otelSpan); - TraceContext.setCurrentTraceData(traceData); - - final var activeSpan = traceData.getActiveSpan(); - final var span = activeSpan.getSpan(); - span.setTraceId("trace-id"); - span.setId("span-id"); - - final Map headers = TraceContextHeadersExtractor.extractHeaders(); - - assertEquals("trace-id", headers.get(WOODY_TRACE_ID)); - assertEquals("span-id", headers.get(WOODY_SPAN_ID)); - assertNull(headers.get(WOODY_PARENT_ID)); - assertNull(headers.get(WOODY_DEADLINE)); - assertNull(headers.get(WOODY_META_ID)); - assertNotNull(headers.get(OTEL_TRACE_PARENT)); - - otelSpan.end(); - } - - @Test - void shouldIncludeRequestMetadata() { - final var traceData = new TraceData(); - final var otelSpan = tracer.spanBuilder("test-span").startSpan(); - traceData.setOtelSpan(otelSpan); - TraceContext.setCurrentTraceData(traceData); - - final var serviceSpan = traceData.getServiceSpan().getSpan(); - serviceSpan.setTraceId("trace-id"); - serviceSpan.setId("span-id"); - traceData.getActiveSpan().getCustomMetadata().putValue(WoodyMetaHeaders.X_REQUEST_ID, "request-123"); - traceData.getActiveSpan().getCustomMetadata() - .putValue(WoodyMetaHeaders.X_REQUEST_DEADLINE, "2030-12-31T23:59:59Z"); - - final Map headers = TraceContextHeadersExtractor.extractHeaders(); - - assertEquals("request-123", headers.get(WOODY_META_REQUEST_ID)); - assertEquals("2030-12-31T23:59:59Z", headers.get(WOODY_META_REQUEST_DEADLINE)); - - otelSpan.end(); - } - - @Test - void shouldThrowWhenSpanContextIsInvalid() { - final var traceData = new TraceData(); - traceData.setOtelSpan(Span.getInvalid()); - TraceContext.setCurrentTraceData(traceData); - - try { - TraceContextHeadersExtractor.extractHeaders(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException e) { - // Expected - } - } - - @Test - void shouldNotIncludeEmptyValues() { - final var traceData = new TraceData(); - final var otelSpan = tracer.spanBuilder("test-span").startSpan(); - traceData.setOtelSpan(otelSpan); - TraceContext.setCurrentTraceData(traceData); - - final var activeSpan = traceData.getActiveSpan(); - final var span = activeSpan.getSpan(); - span.setTraceId("trace-id"); - span.setId("span-id"); - activeSpan.getCustomMetadata().putValue(WoodyMetaHeaders.ID, ""); - activeSpan.getCustomMetadata().putValue(WoodyMetaHeaders.USERNAME, null); - - final Map headers = TraceContextHeadersExtractor.extractHeaders(); - - assertFalse(headers.containsKey(WOODY_META_ID)); - assertFalse(headers.containsKey(WOODY_META_USERNAME)); - - otelSpan.end(); - } - - @Test - void shouldExtractAllUserIdentityMetadata() { - final var traceData = new TraceData(); - final var otelSpan = tracer.spanBuilder("test-span").startSpan(); - traceData.setOtelSpan(otelSpan); - TraceContext.setCurrentTraceData(traceData); - - final var activeSpan = traceData.getActiveSpan(); - final var span = activeSpan.getSpan(); - span.setTraceId("GZvsthKQAAA"); - span.setId("GZvsthKQBBB"); - span.setParentId("undefined"); - - activeSpan.getCustomMetadata().putValue(WoodyMetaHeaders.ID, "b54a93c4-415d-4f33-a5e9-3608fd043ff4"); - activeSpan.getCustomMetadata().putValue(WoodyMetaHeaders.USERNAME, "noreply@valitydev.com"); - activeSpan.getCustomMetadata().putValue(WoodyMetaHeaders.EMAIL, "noreply@valitydev.com"); - activeSpan.getCustomMetadata().putValue(WoodyMetaHeaders.REALM, "/internal"); - - final Map headers = TraceContextHeadersExtractor.extractHeaders(); - - assertEquals("b54a93c4-415d-4f33-a5e9-3608fd043ff4", headers.get(WOODY_META_ID)); - assertEquals("noreply@valitydev.com", headers.get(WOODY_META_USERNAME)); - assertEquals("noreply@valitydev.com", headers.get(WOODY_META_EMAIL)); - assertEquals("/internal", headers.get(WOODY_META_REALM)); - - otelSpan.end(); - } - - @Test - void shouldGenerateValidTraceparent() { - final var traceData = new TraceData(); - final var otelSpan = tracer.spanBuilder("test-span").startSpan(); - traceData.setOtelSpan(otelSpan); - TraceContext.setCurrentTraceData(traceData); - - final var span = traceData.getActiveSpan().getSpan(); - span.setTraceId("trace-id"); - span.setId("span-id"); - - final Map headers = TraceContextHeadersExtractor.extractHeaders(); - - final String traceparent = headers.get(OTEL_TRACE_PARENT); - assertNotNull(traceparent); - assertTrue(traceparent.matches("00-[0-9a-f]{32}-[0-9a-f]{16}-0[0-1]")); - - otelSpan.end(); - } - - @Test - void shouldExtractComplexScenarioWithAllHeaders() { - final var traceData = new TraceData(); - TraceContext.initNewServiceTrace(traceData, WFlow.createDefaultIdGenerator(), WFlow.createDefaultIdGenerator()); - - final var otelSpan = tracer.spanBuilder("test-span").startSpan(); - traceData.setOtelSpan(otelSpan); - TraceContext.setCurrentTraceData(traceData); - - final var activeSpan = traceData.getActiveSpan(); - final var span = activeSpan.getSpan(); - span.setTraceId("GZyWNGugAAA"); - span.setId("GZyWNGugBBB"); - span.setParentId("undefined"); - span.setDeadline(Instant.parse("2030-01-01T00:00:00Z")); - - final var metadata = activeSpan.getCustomMetadata(); - metadata.putValue(WoodyMetaHeaders.ID, "b54a93c4-415d-4f33-a5e9-3608fd043ff4"); - metadata.putValue(WoodyMetaHeaders.USERNAME, "noreply@valitydev.com"); - metadata.putValue(WoodyMetaHeaders.EMAIL, "noreply@valitydev.com"); - metadata.putValue(WoodyMetaHeaders.REALM, "/internal"); - metadata.putValue(WoodyMetaHeaders.X_REQUEST_ID, "req-12345"); - metadata.putValue(WoodyMetaHeaders.X_REQUEST_DEADLINE, "2030-01-01T00:00:00Z"); - - final Map headers = TraceContextHeadersExtractor.extractHeaders(); - - assertEquals("GZyWNGugAAA", headers.get(WOODY_TRACE_ID)); - assertEquals("GZyWNGugBBB", headers.get(WOODY_SPAN_ID)); - assertEquals("undefined", headers.get(WOODY_PARENT_ID)); - assertEquals("2030-01-01T00:00:00Z", headers.get(WOODY_DEADLINE)); - assertEquals("b54a93c4-415d-4f33-a5e9-3608fd043ff4", headers.get(WOODY_META_ID)); - assertEquals("noreply@valitydev.com", headers.get(WOODY_META_USERNAME)); - assertEquals("noreply@valitydev.com", headers.get(WOODY_META_EMAIL)); - assertEquals("/internal", headers.get(WOODY_META_REALM)); - assertEquals("req-12345", headers.get(WOODY_META_REQUEST_ID)); - assertEquals("2030-01-01T00:00:00Z", headers.get(WOODY_META_REQUEST_DEADLINE)); - assertNotNull(headers.get(OTEL_TRACE_PARENT)); - - otelSpan.end(); - } - - @Test - void shouldReturnHeadersWhenTraceDataIsAbsent() throws InterruptedException { - TraceContext.setCurrentTraceData(null); - - var captured = new AtomicReference>(); - var thread = new Thread(() -> { - captured.set(TraceContextHeadersExtractor.extractHeaders()); - }); - thread.start(); - thread.join(); - - var headers = captured.get(); - assertNotNull(headers); - assertNotNull(headers.get(OTEL_TRACE_PARENT)); - } - - @Test - void shouldThrowWhenOtelSpanIsNull() { - final var traceData = new TraceData(); - traceData.setOtelSpan(null); - TraceContext.setCurrentTraceData(traceData); - - assertThrows(IllegalStateException.class, TraceContextHeadersExtractor::extractHeaders); - } -} diff --git a/src/test/java/dev/vality/wachter/tracing/TraceContextHeadersNormalizerTest.java b/src/test/java/dev/vality/wachter/tracing/TraceContextHeadersNormalizerTest.java deleted file mode 100644 index 7537a93..0000000 --- a/src/test/java/dev/vality/wachter/tracing/TraceContextHeadersNormalizerTest.java +++ /dev/null @@ -1,345 +0,0 @@ -package dev.vality.wachter.tracing; - -import dev.vality.wachter.security.JwtTokenDetailsExtractor; -import dev.vality.wachter.security.JwtTokenDetailsExtractor.JwtTokenDetails; -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpHeaders; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.time.Instant; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import static dev.vality.wachter.constants.TraceHeadersConstants.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class TraceContextHeadersNormalizerTest { - - @Mock - private HttpServletRequest request; - - @Mock - private SecurityContext securityContext; - - @Mock - private Authentication authentication; - - @BeforeEach - void setUp() { - SecurityContextHolder.clearContext(); - lenient().when(request.getHeader(WOODY_TRACE_ID)).thenReturn(null); - lenient().when(request.getHeader(WOODY_SPAN_ID)).thenReturn(null); - lenient().when(request.getHeader(WOODY_PARENT_ID)).thenReturn(null); - lenient().when(request.getHeader(WOODY_DEADLINE)).thenReturn(null); - lenient().when(request.getHeader(OTEL_TRACE_PARENT)).thenReturn(null); - lenient().when(request.getHeader(OTEL_TRACE_STATE)).thenReturn(null); - lenient().when(request.getHeader(ExternalHeaders.X_WOODY_TRACE_ID)).thenReturn(null); - lenient().when(request.getHeader(ExternalHeaders.X_WOODY_SPAN_ID)).thenReturn(null); - lenient().when(request.getHeader(ExternalHeaders.X_WOODY_PARENT_ID)).thenReturn(null); - lenient().when(request.getHeader(ExternalHeaders.X_WOODY_DEADLINE)).thenReturn(null); - lenient().when(request.getHeader(ExternalHeaders.X_REQUEST_ID)).thenReturn(null); - lenient().when(request.getHeader(ExternalHeaders.X_REQUEST_DEADLINE)).thenReturn(null); - lenient().when(request.getHeader(ExternalHeaders.X_INVOICE_ID)).thenReturn(null); - } - - @Test - void shouldNormalizeWoodyHeadersFromLowercase() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(List.of( - WOODY_TRACE_ID, WOODY_SPAN_ID, WOODY_PARENT_ID, WOODY_DEADLINE - ))); - when(request.getHeader(WOODY_TRACE_ID)).thenReturn("trace-123"); - when(request.getHeader(WOODY_SPAN_ID)).thenReturn("span-456"); - when(request.getHeader(WOODY_PARENT_ID)).thenReturn("parent-789"); - when(request.getHeader(WOODY_DEADLINE)).thenReturn("2030-01-01T00:00:00Z"); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertEquals("trace-123", normalized.get(WOODY_TRACE_ID)); - assertEquals("span-456", normalized.get(WOODY_SPAN_ID)); - assertEquals("parent-789", normalized.get(WOODY_PARENT_ID)); - assertEquals("2030-01-01T00:00:00Z", normalized.get(WOODY_DEADLINE)); - } - - @Test - void shouldNormalizeXWoodyHeadersToWoody() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(List.of( - ExternalHeaders.X_WOODY_TRACE_ID, ExternalHeaders.X_WOODY_SPAN_ID, ExternalHeaders.X_WOODY_PARENT_ID - ))); - when(request.getHeader(ExternalHeaders.X_WOODY_TRACE_ID)).thenReturn("trace-123"); - when(request.getHeader(ExternalHeaders.X_WOODY_SPAN_ID)).thenReturn("span-456"); - when(request.getHeader(ExternalHeaders.X_WOODY_PARENT_ID)).thenReturn("parent-789"); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertEquals("trace-123", normalized.get(WOODY_TRACE_ID)); - assertEquals("span-456", normalized.get(WOODY_SPAN_ID)); - assertEquals("parent-789", normalized.get(WOODY_PARENT_ID)); - } - - @Test - void shouldNormalizeUserIdentityMetadataFromXWoody() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(List.of( - ExternalHeaders.X_WOODY_META_ID, - ExternalHeaders.X_WOODY_META_USERNAME, - ExternalHeaders.X_WOODY_META_EMAIL, - ExternalHeaders.X_WOODY_META_REALM - ))); - when(request.getHeader(ExternalHeaders.X_WOODY_META_ID)).thenReturn("user-id-123"); - when(request.getHeader(ExternalHeaders.X_WOODY_META_USERNAME)).thenReturn("john.doe"); - when(request.getHeader(ExternalHeaders.X_WOODY_META_EMAIL)).thenReturn("john@example.com"); - when(request.getHeader(ExternalHeaders.X_WOODY_META_REALM)).thenReturn("/internal"); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertEquals("user-id-123", normalized.get(WOODY_META_ID)); - assertEquals("john.doe", normalized.get(WOODY_META_USERNAME)); - assertEquals("john@example.com", normalized.get(WOODY_META_EMAIL)); - assertEquals("/internal", normalized.get(WOODY_META_REALM)); - } - - @Test - void shouldNormalizeTraceparentHeader() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(List.of(OTEL_TRACE_PARENT))); - when(request.getHeader(OTEL_TRACE_PARENT)).thenReturn("00-123abc-456def-01"); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertEquals("00-123abc-456def-01", normalized.get(OTEL_TRACE_PARENT)); - } - - @Test - void shouldMergeJwtMetadataIntoHeaders() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(Collections.emptyList())); - SecurityContextHolder.setContext(securityContext); - when(securityContext.getAuthentication()).thenReturn(authentication); - - try (MockedStatic extractor = mockStatic(JwtTokenDetailsExtractor.class)) { - var tokenDetails = new JwtTokenDetails( - "user-jwt-id", - "jwt-username", - "jwt@email.com", - "/jwt-realm", - List.of("ROLE_USER") - ); - extractor.when(() -> JwtTokenDetailsExtractor.extractFromContext(authentication)) - .thenReturn(Optional.of(tokenDetails)); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertEquals("user-jwt-id", normalized.get(WOODY_META_ID)); - assertEquals("jwt-username", normalized.get(WOODY_META_USERNAME)); - assertEquals("jwt@email.com", normalized.get(WOODY_META_EMAIL)); - assertEquals("/jwt-realm", normalized.get(WOODY_META_REALM)); - } - } - - @Test - void shouldMergeJwtMetadataWithHeaders() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(List.of( - ExternalHeaders.X_WOODY_META_ID - ))); - when(request.getHeader(ExternalHeaders.X_WOODY_META_ID)).thenReturn("header-user-id"); - when(request.getHeader(OTEL_TRACE_PARENT)).thenReturn(null); - - SecurityContextHolder.setContext(securityContext); - when(securityContext.getAuthentication()).thenReturn(authentication); - - try (MockedStatic extractor = mockStatic(JwtTokenDetailsExtractor.class)) { - var tokenDetails = new JwtTokenDetails( - "jwt-user-id", - "jwt-username", - null, - null, - List.of() - ); - extractor.when(() -> JwtTokenDetailsExtractor.extractFromContext(authentication)) - .thenReturn(Optional.of(tokenDetails)); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertEquals("jwt-user-id", normalized.get(WOODY_META_ID)); - assertEquals("jwt-username", normalized.get(WOODY_META_USERNAME)); - } - } - - @Test - void shouldMergeRequestDeadlineToWoodyDeadline() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(Collections.emptyList())); - when(request.getHeader(ExternalHeaders.X_REQUEST_DEADLINE)).thenReturn("2030-12-31T23:59:59Z"); - when(request.getHeader(ExternalHeaders.X_REQUEST_ID)).thenReturn(null); - when(request.getHeader(OTEL_TRACE_PARENT)).thenReturn(null); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertEquals("2030-12-31T23:59:59Z", normalized.get(WOODY_DEADLINE)); - assertEquals("2030-12-31T23:59:59Z", normalized.get(WOODY_META_REQUEST_DEADLINE)); - assertFalse(normalized.containsKey(ExternalHeaders.X_REQUEST_DEADLINE)); - } - - @Test - void shouldMergeRelativeDeadline() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(Collections.emptyList())); - when(request.getHeader(ExternalHeaders.X_REQUEST_DEADLINE)).thenReturn("30s"); - when(request.getHeader(ExternalHeaders.X_REQUEST_ID)).thenReturn("req-123"); - when(request.getHeader(OTEL_TRACE_PARENT)).thenReturn(null); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertNotNull(normalized.get(WOODY_DEADLINE)); - assertEquals(normalized.get(WOODY_DEADLINE), normalized.get(WOODY_META_REQUEST_DEADLINE)); - assertFalse(normalized.containsKey(ExternalHeaders.X_REQUEST_DEADLINE)); - assertTrue(Instant.parse(normalized.get(WOODY_DEADLINE)).isAfter(Instant.now())); - } - - @Test - void shouldNotOverwriteExistingWoodyDeadline() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(List.of(WOODY_DEADLINE))); - when(request.getHeader(WOODY_DEADLINE)).thenReturn("2025-01-01T00:00:00Z"); - when(request.getHeader(ExternalHeaders.X_REQUEST_DEADLINE)).thenReturn("2030-12-31T23:59:59Z"); - when(request.getHeader(ExternalHeaders.X_REQUEST_ID)).thenReturn(null); - when(request.getHeader(OTEL_TRACE_PARENT)).thenReturn(null); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertEquals("2025-01-01T00:00:00Z", normalized.get(WOODY_DEADLINE)); - assertEquals("2030-12-31T23:59:59Z", normalized.get(WOODY_META_REQUEST_DEADLINE)); - assertFalse(normalized.containsKey(ExternalHeaders.X_REQUEST_DEADLINE)); - } - - @Test - void shouldPreserveRequestIdWithoutDeadline() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(Collections.emptyList())); - when(request.getHeader(ExternalHeaders.X_REQUEST_ID)).thenReturn("req-no-deadline"); - when(request.getHeader(OTEL_TRACE_PARENT)).thenReturn(null); - when(request.getHeader(ExternalHeaders.X_REQUEST_DEADLINE)).thenReturn(null); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertEquals("req-no-deadline", normalized.get(WOODY_META_REQUEST_ID)); - assertFalse(normalized.containsKey(WOODY_META_REQUEST_DEADLINE)); - assertFalse(normalized.containsKey(ExternalHeaders.X_REQUEST_ID)); - } - - @Test - void shouldHandleEmptyHeaders() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(Collections.emptyList())); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertTrue(normalized.isEmpty()); - } - - @Test - void shouldIgnoreNullHeaderValues() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(List.of( - WOODY_TRACE_ID, WOODY_SPAN_ID - ))); - when(request.getHeader(WOODY_TRACE_ID)).thenReturn(null); - when(request.getHeader(WOODY_SPAN_ID)).thenReturn("span-456"); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertFalse(normalized.containsKey(WOODY_TRACE_ID)); - assertEquals("span-456", normalized.get(WOODY_SPAN_ID)); - } - - @Test - void shouldHandleComplexScenarioWithAllHeaderTypes() { - when(request.getHeaderNames()).thenReturn(Collections.enumeration(List.of( - WOODY_TRACE_ID, - ExternalHeaders.X_WOODY_SPAN_ID, - ExternalHeaders.X_WOODY_PARENT_ID, - ExternalHeaders.X_WOODY_META_EMAIL, - OTEL_TRACE_PARENT, - "content-type", - "authorization" - ))); - when(request.getHeader(WOODY_TRACE_ID)).thenReturn("GZyWNGugAAA"); - when(request.getHeader(ExternalHeaders.X_WOODY_SPAN_ID)).thenReturn("GZyWNGugBBB"); - when(request.getHeader(ExternalHeaders.X_WOODY_PARENT_ID)).thenReturn("undefined"); - when(request.getHeader(ExternalHeaders.X_WOODY_META_EMAIL)).thenReturn("noreply@valitydev.com"); - when(request.getHeader(OTEL_TRACE_PARENT)).thenReturn( - "00-cfa3d3072a4e3e99fc14829a65311819-6e4609576fa4d077-01"); - when(request.getHeader(ExternalHeaders.X_REQUEST_ID)).thenReturn("req-complex"); - when(request.getHeader(ExternalHeaders.X_REQUEST_DEADLINE)).thenReturn("2030-01-01T00:00:00Z"); - - SecurityContextHolder.setContext(securityContext); - when(securityContext.getAuthentication()).thenReturn(authentication); - - try (MockedStatic extractor = mockStatic(JwtTokenDetailsExtractor.class)) { - var tokenDetails = new JwtTokenDetails( - "b54a93c4-415d-4f33-a5e9-3608fd043ff4", - "noreply@valitydev.com", - "noreply@valitydev.com", - "/internal", - List.of("ROLE_USER") - ); - extractor.when(() -> JwtTokenDetailsExtractor.extractFromContext(authentication)) - .thenReturn(Optional.of(tokenDetails)); - - var normalized = TraceContextHeadersNormalizer.normalize(request); - - assertEquals("GZyWNGugAAA", normalized.get(WOODY_TRACE_ID)); - assertEquals("GZyWNGugBBB", normalized.get(WOODY_SPAN_ID)); - assertEquals("undefined", normalized.get(WOODY_PARENT_ID)); - assertEquals("noreply@valitydev.com", normalized.get(WOODY_META_EMAIL)); - assertEquals("b54a93c4-415d-4f33-a5e9-3608fd043ff4", - normalized.get(WOODY_META_ID)); - assertEquals("noreply@valitydev.com", normalized.get(WOODY_META_USERNAME)); - assertEquals("/internal", normalized.get(WOODY_META_REALM)); - assertEquals("00-cfa3d3072a4e3e99fc14829a65311819-6e4609576fa4d077-01", normalized.get(OTEL_TRACE_PARENT)); - assertEquals("2030-01-01T00:00:00Z", normalized.get(WOODY_DEADLINE)); - assertEquals("req-complex", normalized.get(WOODY_META_REQUEST_ID)); - assertEquals("2030-01-01T00:00:00Z", normalized.get(WOODY_META_REQUEST_DEADLINE)); - assertFalse(normalized.containsKey(ExternalHeaders.X_REQUEST_ID)); - assertFalse(normalized.containsKey(ExternalHeaders.X_REQUEST_DEADLINE)); - } - } - - @Test - void shouldNormalizeResponseHeaders() { - var responseHeaders = new HttpHeaders(); - responseHeaders.add(WOODY_TRACE_ID, "resp-trace"); - responseHeaders.add(WOODY_SPAN_ID, "resp-span"); - responseHeaders.add(WOODY_PARENT_ID, "resp-parent"); - responseHeaders.add(WOODY_META_ID, "resp-user"); - responseHeaders.add(WOODY_META_REQUEST_ID, "resp-req"); - responseHeaders.add(WOODY_META_REQUEST_DEADLINE, "2030-01-01T00:00:00Z"); - responseHeaders.add(WOODY_META_REQUEST_INVOICE_ID, "resp-req"); - responseHeaders.add(WOODY_DEADLINE, "2030-01-01T00:00:00Z"); - responseHeaders.add(WOODY_ERROR_CLASS, "resp-req"); - responseHeaders.add(WOODY_ERROR_REASON, "resp-req"); - responseHeaders.add(OTEL_TRACE_PARENT, "00-abc-def-01"); - responseHeaders.add(OTEL_TRACE_STATE, "00-abc-def-01"); - responseHeaders.add("Content-Type", "application/json"); - responseHeaders.add("Cache-Control", "no-cache"); - - var normalized = TraceContextHeadersNormalizer.normalizeResponseHeaders(responseHeaders); - - assertTrue(normalized.containsKey(ExternalHeaders.X_WOODY_TRACE_ID)); - assertTrue(normalized.containsKey(ExternalHeaders.X_WOODY_SPAN_ID)); - assertTrue(normalized.containsKey(ExternalHeaders.X_WOODY_PARENT_ID)); - assertTrue(normalized.containsKey(ExternalHeaders.X_WOODY_DEADLINE)); - assertTrue(normalized.containsKey(ExternalHeaders.X_WOODY_ERROR_CLASS)); - assertTrue(normalized.containsKey(ExternalHeaders.X_WOODY_ERROR_REASON)); - assertTrue(normalized.containsKey(ExternalHeaders.X_WOODY_META_ID)); - assertTrue(normalized.containsKey(ExternalHeaders.X_REQUEST_ID)); - assertTrue(normalized.containsKey(ExternalHeaders.X_REQUEST_DEADLINE)); - assertTrue(normalized.containsKey(ExternalHeaders.X_INVOICE_ID)); - assertTrue(normalized.containsKey(OTEL_TRACE_PARENT)); - assertTrue(normalized.containsKey(OTEL_TRACE_STATE)); - assertFalse(normalized.containsKey("Content-Type")); - assertFalse(normalized.containsKey("Cache-Control")); - } -} diff --git a/src/test/java/dev/vality/wachter/tracing/TraceContextPipelineTest.java b/src/test/java/dev/vality/wachter/tracing/TraceContextPipelineTest.java deleted file mode 100644 index c2f2144..0000000 --- a/src/test/java/dev/vality/wachter/tracing/TraceContextPipelineTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package dev.vality.wachter.tracing; - -import dev.vality.woody.api.flow.WFlow; -import dev.vality.woody.api.trace.context.TraceContext; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import static dev.vality.wachter.constants.TraceHeadersConstants.*; -import static org.junit.jupiter.api.Assertions.*; - -class TraceContextPipelineTest { - - private SdkTracerProvider tracerProvider; - - @BeforeEach - void setUp() { - GlobalOpenTelemetry.resetForTest(); - tracerProvider = SdkTracerProvider.builder().build(); - var openTelemetry = OpenTelemetrySdk.builder() - .setTracerProvider(tracerProvider) - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .build(); - GlobalOpenTelemetry.set(openTelemetry); - } - - @AfterEach - void tearDown() { - TraceContext.setCurrentTraceData(null); - GlobalOpenTelemetry.resetForTest(); - if (tracerProvider != null) { - tracerProvider.close(); - } - } - - @Test - void shouldRestoreAndExtractTraceContextWithinWFlow() { - var normalized = new HashMap(); - normalized.put(WOODY_TRACE_ID, "GZyWNGugAAA"); - normalized.put(WOODY_SPAN_ID, "GZyWNGugBBB"); - normalized.put(WOODY_PARENT_ID, "undefined"); - normalized.put(WOODY_DEADLINE, "2030-01-01T00:00:00Z"); - var otelTraceId = "3d8202ad198e4d37771c995246e1b356"; - normalized.put(OTEL_TRACE_PARENT, "00-" + otelTraceId + "-9cfa814ae977266e-01"); - normalized.put(WOODY_META_ID, "user-id"); - normalized.put(WOODY_META_USERNAME, "user-name"); - normalized.put(WOODY_META_EMAIL, "user@example.com"); - normalized.put(WOODY_META_REALM, "/internal"); - normalized.put(WOODY_META_REQUEST_ID, "request-id"); - normalized.put(WOODY_META_REQUEST_DEADLINE, "2030-01-01T00:00:00Z"); - - var traceData = TraceContextRestorer.restoreTraceData(normalized); - assertTrue(traceData.getServiceSpan().getSpan().isFilled()); - assertFalse(traceData.isClient()); - var extractedRef = new AtomicReference>(); - - WFlow.create(() -> { - var current = TraceContext.getCurrentTraceData(); - assertNotNull(current); - assertTrue(current.getServiceSpan().getSpan().isFilled()); - assertFalse(current.isClient()); - extractedRef.set(TraceContextHeadersExtractor.extractHeaders()); - }, traceData).run(); - - var extracted = extractedRef.get(); - assertNotNull(extracted); - assertEquals("GZyWNGugAAA", extracted.get(WOODY_TRACE_ID)); - assertEquals("GZyWNGugBBB", extracted.get(WOODY_SPAN_ID)); - assertEquals("undefined", extracted.get(WOODY_PARENT_ID)); - assertEquals("2030-01-01T00:00:00Z", extracted.get(WOODY_DEADLINE)); - assertEquals("user-id", extracted.get(WOODY_META_ID)); - assertEquals("user-name", extracted.get(WOODY_META_USERNAME)); - assertEquals("user@example.com", extracted.get(WOODY_META_EMAIL)); - assertEquals("/internal", extracted.get(WOODY_META_REALM)); - assertEquals("request-id", extracted.get(WOODY_META_REQUEST_ID)); - assertEquals("2030-01-01T00:00:00Z", extracted.get(WOODY_META_REQUEST_DEADLINE)); - assertTrue(extracted.get(OTEL_TRACE_PARENT).contains(otelTraceId)); - } -} diff --git a/src/test/java/dev/vality/wachter/tracing/TraceContextRestorerTest.java b/src/test/java/dev/vality/wachter/tracing/TraceContextRestorerTest.java deleted file mode 100644 index aef69d9..0000000 --- a/src/test/java/dev/vality/wachter/tracing/TraceContextRestorerTest.java +++ /dev/null @@ -1,273 +0,0 @@ -package dev.vality.wachter.tracing; - -import dev.vality.woody.api.trace.TraceData; -import dev.vality.woody.api.trace.context.TraceContext; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; - -import static dev.vality.wachter.constants.TraceHeadersConstants.*; -import static org.junit.jupiter.api.Assertions.*; - -class TraceContextRestorerTest { - - private SdkTracerProvider tracerProvider; - - @BeforeEach - void setUp() { - GlobalOpenTelemetry.resetForTest(); - tracerProvider = SdkTracerProvider.builder().build(); - var openTelemetry = OpenTelemetrySdk.builder() - .setTracerProvider(tracerProvider) - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .build(); - GlobalOpenTelemetry.set(openTelemetry); - } - - @AfterEach - void tearDown() { - TraceContext.setCurrentTraceData(null); - GlobalOpenTelemetry.resetForTest(); - if (tracerProvider != null) { - tracerProvider.close(); - } - } - - @Test - void shouldRestoreWoodyTraceHeaders() { - var headers = Map.of( - WOODY_TRACE_ID, "GZyWNGugAAA", - WOODY_SPAN_ID, "GZyWNGugBBB", - WOODY_PARENT_ID, "undefined", - WOODY_DEADLINE, "2030-01-01T00:00:00Z" - ); - - TraceData traceData = TraceContextRestorer.restoreTraceData(headers); - - assertNotNull(traceData); - var span = traceData.getServiceSpan().getSpan(); - assertEquals("GZyWNGugAAA", span.getTraceId()); - assertEquals("GZyWNGugBBB", span.getId()); - assertEquals("undefined", span.getParentId()); - assertEquals(Instant.parse("2030-01-01T00:00:00Z"), span.getDeadline()); - assertNotNull(traceData.getServiceSpan().getSpan().getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getId()); - assertTrue(traceData.getOtelSpan().getSpanContext().isValid()); - assertNotNull(traceData.getOtelSpan().getSpanContext().getTraceId()); - } - - @Test - void shouldRestoreUserIdentityMetadata() { - var headers = new HashMap(); - headers.put(WOODY_TRACE_ID, "trace-123"); - headers.put(WOODY_META_ID, "b54a93c4-415d-4f33-a5e9-3608fd043ff4"); - headers.put(WOODY_META_USERNAME, "noreply@valitydev.com"); - headers.put(WOODY_META_EMAIL, "noreply@valitydev.com"); - headers.put(WOODY_META_REALM, "/internal"); - - TraceData traceData = TraceContextRestorer.restoreTraceData(headers); - - var metadata = traceData.getActiveSpan().getCustomMetadata(); - assertEquals("b54a93c4-415d-4f33-a5e9-3608fd043ff4", metadata.getValue(WoodyMetaHeaders.ID)); - assertEquals("noreply@valitydev.com", metadata.getValue(WoodyMetaHeaders.USERNAME)); - assertEquals("noreply@valitydev.com", metadata.getValue(WoodyMetaHeaders.EMAIL)); - assertEquals("/internal", metadata.getValue(WoodyMetaHeaders.REALM)); - assertNotNull(traceData.getServiceSpan().getSpan().getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getId()); - assertTrue(traceData.getOtelSpan().getSpanContext().isValid()); - assertNotNull(traceData.getOtelSpan().getSpanContext().getTraceId()); - } - - @Test - void shouldRestoreRequestMetadata() { - var headers = Map.of( - WOODY_TRACE_ID, "trace-123", - WOODY_META_REQUEST_ID, "req-456", - WOODY_META_REQUEST_DEADLINE, "2030-12-31T23:59:59Z" - ); - - TraceData traceData = TraceContextRestorer.restoreTraceData(headers); - - var metadata = traceData.getActiveSpan().getCustomMetadata(); - assertEquals("req-456", metadata.getValue(WoodyMetaHeaders.X_REQUEST_ID)); - assertEquals("2030-12-31T23:59:59Z", metadata.getValue(WoodyMetaHeaders.X_REQUEST_DEADLINE)); - assertNotNull(traceData.getServiceSpan().getSpan().getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getId()); - assertTrue(traceData.getOtelSpan().getSpanContext().isValid()); - assertNotNull(traceData.getOtelSpan().getSpanContext().getTraceId()); - } - - @Test - void shouldRestoreTraceparentAndCreateOtelSpan() { - var traceId = "cfa3d3072a4e3e99fc14829a65311819"; - var headers = Map.of( - WOODY_TRACE_ID, "trace-123", - OTEL_TRACE_PARENT, "00-" + traceId + "-6e4609576fa4d077-01" - ); - - TraceData traceData = TraceContextRestorer.restoreTraceData(headers); - - assertEquals("00-" + traceId + "-6e4609576fa4d077-01", traceData.getInboundTraceParent()); - var parentContext = Span.fromContext(traceData.consumePendingParentContext()).getSpanContext(); - assertTrue(parentContext.isValid()); - assertEquals(traceId, parentContext.getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getId()); - assertTrue(traceData.getOtelSpan().getSpanContext().isValid()); - assertNotNull(traceData.getOtelSpan().getSpanContext().getTraceId()); - } - - @Test - void shouldHandleEmptyHeaders() { - TraceData traceData = TraceContextRestorer.restoreTraceData(Map.of()); - - assertNotNull(traceData.getServiceSpan().getSpan().getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getId()); - assertTrue(traceData.getOtelSpan().getSpanContext().isValid()); - assertNotNull(traceData.getOtelSpan().getSpanContext().getTraceId()); - } - - @Test - void shouldHandlePartialHeaders() { - var headers = Map.of( - WOODY_TRACE_ID, "trace-123" - ); - - TraceData traceData = TraceContextRestorer.restoreTraceData(headers); - - assertEquals("trace-123", traceData.getServiceSpan().getSpan().getTraceId()); - assertNull(traceData.getServiceSpan().getSpan().getDeadline()); - assertNotNull(traceData.getServiceSpan().getSpan().getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getId()); - assertTrue(traceData.getOtelSpan().getSpanContext().isValid()); - assertNotNull(traceData.getOtelSpan().getSpanContext().getTraceId()); - } - - @Test - void shouldHandleInvalidDeadline() { - var headers = Map.of( - WOODY_TRACE_ID, "trace-123", - WOODY_DEADLINE, "invalid-date" - ); - - TraceData traceData = TraceContextRestorer.restoreTraceData(headers); - - assertEquals("trace-123", traceData.getServiceSpan().getSpan().getTraceId()); - assertNull(traceData.getServiceSpan().getSpan().getDeadline()); - assertNotNull(traceData.getServiceSpan().getSpan().getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getId()); - assertTrue(traceData.getOtelSpan().getSpanContext().isValid()); - assertNotNull(traceData.getOtelSpan().getSpanContext().getTraceId()); - } - - @Test - void shouldHandleComplexScenarioWithAllData() { - var headers = new HashMap(); - headers.put(WOODY_TRACE_ID, "GZvsthKQAAA"); - headers.put(WOODY_SPAN_ID, "GZvsthKQBBB"); - headers.put(WOODY_PARENT_ID, "parent-123"); - headers.put(WOODY_DEADLINE, "2030-06-15T12:30:00Z"); - var otelTraceId = "3d8202ad198e4d37771c995246e1b356"; - headers.put(OTEL_TRACE_PARENT, "00-" + otelTraceId + "-9cfa814ae977266e-01"); - headers.put(WOODY_META_ID, "user-uuid"); - headers.put(WOODY_META_USERNAME, "john.doe"); - headers.put(WOODY_META_EMAIL, "john@example.com"); - headers.put(WOODY_META_REALM, "/external"); - headers.put(WOODY_META_REQUEST_ID, "complex-request-id"); - headers.put(WOODY_META_REQUEST_DEADLINE, "2030-06-15T13:00:00Z"); - - TraceData traceData = TraceContextRestorer.restoreTraceData(headers); - - var span = traceData.getServiceSpan().getSpan(); - assertEquals("GZvsthKQAAA", span.getTraceId()); - assertEquals("GZvsthKQBBB", span.getId()); - assertEquals("parent-123", span.getParentId()); - assertEquals(Instant.parse("2030-06-15T12:30:00Z"), span.getDeadline()); - - var metadata = traceData.getActiveSpan().getCustomMetadata(); - assertEquals("user-uuid", metadata.getValue(WoodyMetaHeaders.ID)); - assertEquals("john.doe", metadata.getValue(WoodyMetaHeaders.USERNAME)); - assertEquals("john@example.com", metadata.getValue(WoodyMetaHeaders.EMAIL)); - assertEquals("/external", metadata.getValue(WoodyMetaHeaders.REALM)); - assertEquals("complex-request-id", metadata.getValue(WoodyMetaHeaders.X_REQUEST_ID)); - assertEquals("2030-06-15T13:00:00Z", metadata.getValue(WoodyMetaHeaders.X_REQUEST_DEADLINE)); - - assertEquals("00-" + otelTraceId + "-9cfa814ae977266e-01", traceData.getInboundTraceParent()); - var parentContext = Span.fromContext(traceData.consumePendingParentContext()).getSpanContext(); - assertTrue(parentContext.isValid()); - assertEquals(otelTraceId, parentContext.getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getId()); - assertTrue(traceData.getOtelSpan().getSpanContext().isValid()); - assertNotNull(traceData.getOtelSpan().getSpanContext().getTraceId()); - } - - @Test - void shouldHandleNullAndEmptyValues() { - var headers = new HashMap(); - headers.put(WOODY_TRACE_ID, "trace-123"); - headers.put(WOODY_SPAN_ID, ""); - headers.put(WOODY_PARENT_ID, null); - headers.put(WOODY_META_ID, ""); - headers.put(WOODY_META_USERNAME, null); - - TraceData traceData = TraceContextRestorer.restoreTraceData(headers); - - var span = traceData.getServiceSpan().getSpan(); - assertEquals("trace-123", span.getTraceId()); - assertNotNull(span.getId()); - assertNotNull(span.getParentId()); - - var metadata = traceData.getActiveSpan().getCustomMetadata(); - assertNull(metadata.getValue(WoodyMetaHeaders.ID)); - assertNull(metadata.getValue(WoodyMetaHeaders.USERNAME)); - assertNotNull(traceData.getServiceSpan().getSpan().getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getId()); - assertTrue(traceData.getOtelSpan().getSpanContext().isValid()); - assertNotNull(traceData.getOtelSpan().getSpanContext().getTraceId()); - } - - @Test - void shouldHandleInvalidTraceparentGracefully() { - var headers = Map.of( - WOODY_TRACE_ID, "trace-123", - OTEL_TRACE_PARENT, "invalid-traceparent" - ); - - TraceData traceData = TraceContextRestorer.restoreTraceData(headers); - - assertEquals("trace-123", traceData.getServiceSpan().getSpan().getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getTraceId()); - assertNotNull(traceData.getServiceSpan().getSpan().getId()); - assertTrue(traceData.getOtelSpan().getSpanContext().isValid()); - assertNotNull(traceData.getOtelSpan().getSpanContext().getTraceId()); - } - - @Test - void shouldRestoreMetadataWithSpecialCharacters() { - var headers = new HashMap(); - headers.put(WOODY_TRACE_ID, "trace-123"); - headers.put(WOODY_META_USERNAME, "user@domain.com"); - headers.put(WOODY_META_EMAIL, "user+test@domain.com"); - headers.put(WOODY_META_REALM, "/realm/with/slashes"); - headers.put(WOODY_META_REQUEST_ID, "req-with-dashes-123"); - - TraceData traceData = TraceContextRestorer.restoreTraceData(headers); - - var metadata = traceData.getActiveSpan().getCustomMetadata(); - assertEquals("user@domain.com", metadata.getValue(WoodyMetaHeaders.USERNAME)); - assertEquals("user+test@domain.com", metadata.getValue(WoodyMetaHeaders.EMAIL)); - assertEquals("/realm/with/slashes", metadata.getValue(WoodyMetaHeaders.REALM)); - assertEquals("req-with-dashes-123", metadata.getValue(WoodyMetaHeaders.X_REQUEST_ID)); - } -} diff --git a/src/test/java/dev/vality/wachter/tracing/WoodyTracingFilterTest.java b/src/test/java/dev/vality/wachter/tracing/WoodyTracingFilterTest.java deleted file mode 100644 index 0e55046..0000000 --- a/src/test/java/dev/vality/wachter/tracing/WoodyTracingFilterTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package dev.vality.wachter.tracing; - -import dev.vality.woody.api.trace.context.TraceContext; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import jakarta.servlet.FilterChain; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; - -import static dev.vality.wachter.constants.TraceHeadersConstants.ExternalHeaders.X_WOODY_SPAN_ID; -import static dev.vality.wachter.constants.TraceHeadersConstants.ExternalHeaders.X_WOODY_TRACE_ID; -import static org.junit.jupiter.api.Assertions.assertEquals; - -class WoodyTracingFilterTest { - - private SdkTracerProvider tracerProvider; - private WoodyTracingFilter filter; - - @BeforeEach - void setUp() { - GlobalOpenTelemetry.resetForTest(); - tracerProvider = SdkTracerProvider.builder().build(); - final var openTelemetry = OpenTelemetrySdk.builder() - .setTracerProvider(tracerProvider) - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .build(); - GlobalOpenTelemetry.set(openTelemetry); - filter = new WoodyTracingFilter(8080, "/wachter"); - } - - @AfterEach - void tearDown() { - TraceContext.setCurrentTraceData(null); - GlobalOpenTelemetry.resetForTest(); - if (tracerProvider != null) { - tracerProvider.close(); - } - } - - @Test - void shouldInitializeTraceContext() throws Exception { - final var request = new MockHttpServletRequest("POST", "/wachter"); - final var response = new MockHttpServletResponse(); - request.setLocalPort(8080); - request.addHeader(X_WOODY_TRACE_ID, "test-trace"); - request.addHeader(X_WOODY_SPAN_ID, "test-span"); - - filter.doFilter(request, response, new MockFilterChain()); - - assertEquals(200, response.getStatus()); - } - - @Test - void shouldHandleRequestCorrectly() throws Exception { - final var request = new MockHttpServletRequest("GET", "/wachter"); - final var response = new MockHttpServletResponse(); - request.setLocalPort(8080); - - filter.doFilter(request, response, new MockFilterChain()); - - assertEquals(200, response.getStatus()); - } - - @Test - void shouldSetSpanStatusErrorForServerError() throws Exception { - final var request = new MockHttpServletRequest("GET", "/wachter"); - final var response = new MockHttpServletResponse(); - request.setLocalPort(8080); - final FilterChain chain = (req, res) -> { - ((MockHttpServletResponse) res).setStatus(503); - }; - - filter.doFilter(request, response, chain); - - assertEquals(503, response.getStatus()); - } -}