Spring Cloud Deployer feature (#1925)

* Spring Cloud Deployer; fixes gh-1905

* Polish
This commit is contained in:
Marcin Grzejszczak
2021-04-26 15:19:51 +02:00
committed by GitHub
parent c4ab9dcd49
commit 0f23f9d52f
17 changed files with 2351 additions and 1373 deletions

View File

@@ -19,22 +19,16 @@ package org.springframework.cloud.sleuth.instrument.circuitbreaker;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
import org.springframework.cloud.sleuth.CurrentTraceContext;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceContext;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.reactor.ReactorSleuth;
class TraceReactiveCircuitBreaker implements ReactiveCircuitBreaker {
private static final Log log = LogFactory.getLog(TraceReactiveCircuitBreaker.class);
private final ReactiveCircuitBreaker delegate;
private final Tracer tracer;
@@ -71,68 +65,11 @@ class TraceReactiveCircuitBreaker implements ReactiveCircuitBreaker {
}
private <T> Mono<T> runAndTraceMono(Supplier<Mono<T>> mono) {
return Mono.deferContextual(contextView -> {
Span span = contextView.get(Span.class);
Tracer.SpanInScope scope = contextView.get(Tracer.SpanInScope.class);
return mono.get().doOnError(span::error).doFinally(signalType -> {
span.end();
scope.close();
});
}).contextWrite(this::enhanceContext);
return ReactorSleuth.tracedMono(this.tracer, this.currentTraceContext, "function", mono);
}
private <T> Flux<T> runAndTraceFlux(Supplier<Flux<T>> flux) {
return Flux.deferContextual(contextView -> {
Span span = contextView.get(Span.class);
Tracer.SpanInScope scope = contextView.get(Tracer.SpanInScope.class);
return flux.get().doOnError(span::error).doFinally(signalType -> {
span.end();
scope.close();
});
}).contextWrite(this::enhanceContext);
}
private Span spanFromContext(reactor.util.context.Context context) {
TraceContext traceContext = context.getOrDefault(TraceContext.class, null);
Span span = null;
if (traceContext == null) {
span = context.getOrDefault(Span.class, null);
}
if (traceContext == null && span == null) {
span = this.tracer.nextSpan();
if (log.isDebugEnabled()) {
log.debug("There was no previous span in reactor context, created a new one [" + span + "]");
}
}
else if (traceContext != null) {
// there was a previous span - we create a child one
try (CurrentTraceContext.Scope scope = this.currentTraceContext.maybeScope(traceContext)) {
if (log.isDebugEnabled()) {
log.debug("Found a trace context in reactor context [" + traceContext + "]");
}
span = this.tracer.nextSpan();
if (log.isDebugEnabled()) {
log.debug("Created a child span [" + span + "]");
}
}
}
else {
if (log.isDebugEnabled()) {
log.debug("Found a span in reactor context [" + span + "]");
}
span = this.tracer.nextSpan(span);
if (log.isDebugEnabled()) {
log.debug("Created a child span [" + span + "]");
}
}
// TODO: Better name?
return span.name("function");
}
private Context enhanceContext(Context context) {
Span span = spanFromContext(context);
return context.put(Span.class, span).put(TraceContext.class, span.context()).put(Tracer.SpanInScope.class,
this.tracer.withSpan(span));
return ReactorSleuth.tracedFlux(this.tracer, this.currentTraceContext, "function", flux);
}
}

View File

@@ -0,0 +1,268 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.deployer;
import java.time.Duration;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.deployer.spi.app.AppDeployer;
import org.springframework.cloud.deployer.spi.app.AppScaleRequest;
import org.springframework.cloud.deployer.spi.app.AppStatus;
import org.springframework.cloud.deployer.spi.app.DeploymentState;
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;
import org.springframework.cloud.sleuth.CurrentTraceContext;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.reactor.ReactorSleuth;
import org.springframework.core.env.Environment;
/**
* Trace representation of an {@link AppDeployer}.
*
* @author Marcin Grzejszczak
* @since 3.1.0
*/
public class TraceAppDeployer implements AppDeployer {
private static final Log log = LogFactory.getLog(TraceAppDeployer.class);
private final AppDeployer delegate;
private final BeanFactory beanFactory;
private final Environment environment;
private Tracer tracer;
private CurrentTraceContext currentTraceContext;
private Long pollDelay;
public TraceAppDeployer(AppDeployer delegate, BeanFactory beanFactory, Environment environment) {
this.delegate = delegate;
this.beanFactory = beanFactory;
this.environment = environment;
}
@Override
public String deploy(AppDeploymentRequest request) {
Span span = tracer().nextSpan().name("deploy");
// TODO: Is this secure to pass?
// TODO: Does it make sense?
// if (!request.getCommandlineArguments().isEmpty()) {
// span.tag("commandlineArguments", request.getCommandlineArguments().toString());
// }
// if (!request.getDeploymentProperties().isEmpty()) {
// span.tag("deploymentProperties", request.getDeploymentProperties().toString());
// }
try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) {
span.event("start");
String id = this.delegate.deploy(request);
span.tag("id", id);
registerListener(span, id);
return id;
}
}
private void registerListener(Span span, String id) {
PreviousAndCurrentStatus previousAndCurrentStatus = new PreviousAndCurrentStatus(span);
// @formatter:off
this.delegate.statusReactive(id)
.map(previousAndCurrentStatus::updateCurrent)
.repeatWhen(repeat -> repeat.flatMap(i -> Mono.delay(Duration.ofMillis(pollDelay()))))
.takeUntil(PreviousAndCurrentStatus::isFinished)
.last()
.doOnNext(PreviousAndCurrentStatus::annotateSpan)
.doOnError(span::error)
// we will close the span in the reactive part
.doFinally(signalType -> span.end()).subscribe();
// @formatter:on
}
@Override
public void undeploy(String id) {
Span span = tracer().nextSpan().name("undeploy");
span.tag("id", id);
try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) {
span.event("start");
this.delegate.undeploy(id);
registerListener(span, id);
}
finally {
span.end();
}
}
@Override
public AppStatus status(String id) {
Span span = tracer().nextSpan().name("status");
span.tag("id", id);
try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) {
return this.delegate.status(id);
}
finally {
span.end();
}
}
@Override
public Mono<AppStatus> statusReactive(String id) {
return ReactorSleuth.tracedMono(tracer(), currentTraceContext(), "status",
() -> this.delegate.statusReactive(id), span -> span.tag("id", id));
}
@Override
public Flux<AppStatus> statusesReactive(String... ids) {
return ReactorSleuth.tracedFlux(tracer(), currentTraceContext(), "statuses",
() -> this.delegate.statusesReactive(ids), span -> span.tag("ids", Arrays.toString(ids)));
}
@Override
public RuntimeEnvironmentInfo environmentInfo() {
return this.delegate.environmentInfo();
}
@Override
public String getLog(String id) {
Span span = tracer().nextSpan().name("getLog");
span.tag("id", id);
try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) {
return this.delegate.getLog(id);
}
finally {
span.end();
}
}
@Override
public void scale(AppScaleRequest appScaleRequest) {
Span span = tracer().nextSpan().name("scale");
span.tag("deploymentId", appScaleRequest.getDeploymentId());
span.tag("count", String.valueOf(appScaleRequest.getCount()));
// TODO: Is this secure to pass?
// TODO: Does it make sense?
// if (appScaleRequest.getProperties().isPresent() &&
// !appScaleRequest.getProperties().get().isEmpty()) {
// span.tag("properties", appScaleRequest.getProperties().get().toString());
// }
try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) {
this.delegate.scale(appScaleRequest);
}
finally {
span.end();
}
}
private Tracer tracer() {
if (this.tracer == null) {
this.tracer = this.beanFactory.getBean(Tracer.class);
}
return this.tracer;
}
private CurrentTraceContext currentTraceContext() {
if (this.currentTraceContext == null) {
this.currentTraceContext = this.beanFactory.getBean(CurrentTraceContext.class);
}
return this.currentTraceContext;
}
private long pollDelay() {
if (this.pollDelay == null) {
this.pollDelay = this.environment.getProperty("spring.sleuth.deployer.status-poll-delay", Long.class, 500L);
}
return this.pollDelay;
}
private static final class PreviousAndCurrentStatus {
private final Span span;
private AppStatus current;
private AppStatus previous;
private PreviousAndCurrentStatus(Span span) {
this.span = span;
if (log.isDebugEnabled()) {
log.debug("Current span is [" + span + "]");
}
}
private PreviousAndCurrentStatus updateCurrent(AppStatus current) {
if (log.isTraceEnabled()) {
log.trace("State before change: current [" + this.current + "], previous [" + this.previous + "]");
}
this.previous = this.current;
this.current = current;
if (log.isTraceEnabled()) {
log.trace("State after change: current [" + this.current + "], previous [" + this.previous + "]");
}
if (statusChanged()) {
annotateSpan();
}
else if (log.isTraceEnabled()) {
log.trace("State has not changed, will not annotate the span");
}
return this;
}
private void annotateSpan() {
String name = this.current.getState().name();
if (log.isDebugEnabled()) {
log.debug("Will annotate its state with [" + name + "]");
}
this.span.event(name);
}
private boolean statusChanged() {
if (this.previous == null && this.current != null) {
if (log.isDebugEnabled()) {
log.debug("Previous is null, current is not null");
}
return true;
}
else if (this.current == null) {
throw new IllegalStateException("Current state can't be null");
}
DeploymentState currentState = this.current.getState();
DeploymentState previousState = this.previous.getState();
return currentState != previousState;
}
private boolean isFinished() {
boolean finished = this.current.getState() == DeploymentState.deployed
|| this.current.getState() == DeploymentState.undeployed
|| this.current.getState() == DeploymentState.failed
|| this.current.getState() == DeploymentState.error
|| this.current.getState() == DeploymentState.unknown;
if (log.isTraceEnabled()) {
log.trace("Status is finished [" + finished + "]");
}
return finished;
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.deployer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.deployer.spi.app.AppDeployer;
import org.springframework.core.env.Environment;
/**
* {@link BeanPostProcessor} to wrap a {@link AppDeployer} instance into its trace
* representation.
*
* @author Marcin Grzejszczak
* @since 2.0.0
*/
public class TraceAppDeployerBeanPostProcessor implements BeanPostProcessor {
private final BeanFactory beanFactory;
private final Environment environment;
public TraceAppDeployerBeanPostProcessor(BeanFactory beanFactory, Environment environment) {
this.beanFactory = beanFactory;
this.environment = environment;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof AppDeployer && !(bean instanceof TraceAppDeployer)) {
return new TraceAppDeployer((AppDeployer) bean, this.beanFactory, this.environment);
}
return bean;
}
}

View File

@@ -60,13 +60,13 @@ import org.springframework.util.Assert;
* .scan((l, r) -> l + r) // (-)
* .doOnNext(it -> { // (-)
* //log
* })
* })
* .doFirst(() -> { // (-)
* //log
* })
* })
* .doFinally(signalType -> { // (-)
* //log
* })
* })
* .subscribeOn(Schedulers.parallel()) //(+)
* .subscribe();//(*)
* (*) - captures tracing context if it differs from what was captured before at subscription and propagates it.
@@ -84,7 +84,7 @@ import org.springframework.util.Assert;
* .map(it -> ...) // (+) is SYNC but should add hook as previous Processor/operator does not use hooks
* .doOnNext(it -> { // (-) is SYNC no need to wrap
* //log
* })
* })
* .subscribe();
*}</pre>
*/
@@ -93,6 +93,7 @@ final class ReactorHooksHelper {
// need a way to determine SYNC sources to not add redundant scope passing decorator
// most of reactor-core SYNC sources are marked with SourceProducer interface
static final Class<?> sourceProducerClass;
static {
Class<?> c;
try {

View File

@@ -17,8 +17,10 @@
package org.springframework.cloud.sleuth.instrument.reactor;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -28,16 +30,20 @@ import org.reactivestreams.Subscription;
import reactor.core.CoreSubscriber;
import reactor.core.Fuseable;
import reactor.core.Scannable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Operators;
import reactor.util.annotation.Nullable;
import reactor.util.context.Context;
import org.springframework.cloud.sleuth.CurrentTraceContext;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceContext;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.internal.LazyBean;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
/**
* Reactive Span pointcuts factories.
@@ -314,6 +320,132 @@ public abstract class ReactorSleuth {
};
}
/**
* Wraps the given Mono in a trace representation. Retrieves the span from context,
* creates a child span with the given name.
* @param tracer - Tracer bean
* @param currentTraceContext - CurrentTraceContext bean
* @param childSpanName - name of the created child span
* @param supplier - supplier of a {@link Mono} to be wrapped in tracing
* @param <T> - type returned by the Mono
* @param spanCustomizer - customizer for the child span
* @return traced Mono
*/
public static <T> Mono<T> tracedMono(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext,
@NonNull String childSpanName, @NonNull Supplier<Mono<T>> supplier,
@NonNull Consumer<Span> spanCustomizer) {
return Mono.deferContextual(contextView -> {
Span span = contextView.get(Span.class);
spanCustomizer.accept(span);
Tracer.SpanInScope scope = contextView.get(Tracer.SpanInScope.class);
return supplier.get().doOnError(span::error).doFinally(signalType -> {
span.end();
scope.close();
});
}).contextWrite(context -> ReactorSleuth.enhanceContext(tracer, currentTraceContext, context, childSpanName));
}
/**
* Wraps the given Mono in a trace representation. Retrieves the span from context,
* creates a child span with the given name.
* @param tracer - Tracer bean
* @param currentTraceContext - CurrentTraceContext bean
* @param childSpanName - name of the created child span
* @param supplier - supplier of a {@link Mono} to be wrapped in tracing
* @param <T> - type returned by the Mono
* @return traced Mono
*/
public static <T> Mono<T> tracedMono(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext,
@NonNull String childSpanName, @NonNull Supplier<Mono<T>> supplier) {
return tracedMono(tracer, currentTraceContext, childSpanName, supplier, span -> {
});
}
/**
* Wraps the given Flux in a trace representation. Retrieves the span from context,
* creates a child span with the given name.
* @param tracer - Tracer bean
* @param currentTraceContext - CurrentTraceContext bean
* @param childSpanName - name of the created child span
* @param supplier - supplier of a {@link Flux} to be wrapped in tracing
* @param <T> - type returned by the Flux
* @param spanCustomizer - customizer for the child span
* @return traced Flux
*/
public static <T> Flux<T> tracedFlux(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext,
@NonNull String childSpanName, @NonNull Supplier<Flux<T>> supplier,
@NonNull Consumer<Span> spanCustomizer) {
return Flux.deferContextual(contextView -> {
Span span = contextView.get(Span.class);
spanCustomizer.accept(span);
Tracer.SpanInScope scope = contextView.get(Tracer.SpanInScope.class);
return supplier.get().doOnError(span::error).doFinally(signalType -> {
span.end();
scope.close();
});
}).contextWrite(context -> ReactorSleuth.enhanceContext(tracer, currentTraceContext, context, childSpanName));
}
/**
* Wraps the given Flux in a trace representation. Retrieves the span from context,
* creates a child span with the given name.
* @param tracer - Tracer bean
* @param currentTraceContext - CurrentTraceContext bean
* @param childSpanName - name of the created child span
* @param supplier - supplier of a {@link Flux} to be wrapped in tracing
* @param <T> - type returned by the Flux
* @return traced Flux
*/
public static <T> Flux<T> tracedFlux(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext,
@NonNull String childSpanName, @NonNull Supplier<Flux<T>> supplier) {
return tracedFlux(tracer, currentTraceContext, childSpanName, supplier, span -> {
});
}
private static Span spanFromContext(Tracer tracer, CurrentTraceContext currentTraceContext,
reactor.util.context.Context context, String childSpanName) {
TraceContext traceContext = context.getOrDefault(TraceContext.class, null);
Span span = null;
if (traceContext == null) {
span = context.getOrDefault(Span.class, null);
}
if (traceContext == null && span == null) {
span = tracer.nextSpan();
if (log.isDebugEnabled()) {
log.debug("There was no previous span in reactor context, created a new one [" + span + "]");
}
}
else if (traceContext != null) {
// there was a previous span - we create a child one
try (CurrentTraceContext.Scope scope = currentTraceContext.maybeScope(traceContext)) {
if (log.isDebugEnabled()) {
log.debug("Found a trace context in reactor context [" + traceContext + "]");
}
span = tracer.nextSpan();
if (log.isDebugEnabled()) {
log.debug("Created a child span [" + span + "]");
}
}
}
else {
if (log.isDebugEnabled()) {
log.debug("Found a span in reactor context [" + span + "]");
}
span = tracer.nextSpan(span);
if (log.isDebugEnabled()) {
log.debug("Created a child span [" + span + "]");
}
}
return span.name(childSpanName).start();
}
private static Context enhanceContext(Tracer tracer, CurrentTraceContext currentTraceContext,
reactor.util.context.Context context, String childSpanName) {
Span span = spanFromContext(tracer, currentTraceContext, context, childSpanName);
return context.put(Span.class, span).put(TraceContext.class, span.context()).put(Tracer.SpanInScope.class,
tracer.withSpan(span));
}
}
class SleuthContextOperator<T> implements Subscription, CoreSubscriber<T>, Scannable {

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.deployer;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import org.springframework.cloud.sleuth.CurrentTraceContext;
import org.springframework.cloud.sleuth.TraceContext;
/**
* A noop implementation. Does nothing.
*
* @author Marcin Grzejszczak
* @since 3.0.0
*/
class NoOpCurrentTraceContext implements CurrentTraceContext {
@Override
public TraceContext context() {
return null;
}
@Override
public Scope newScope(TraceContext context) {
return () -> {
};
}
@Override
public Scope maybeScope(TraceContext context) {
return () -> {
};
}
@Override
public <C> Callable<C> wrap(Callable<C> task) {
return task;
}
@Override
public Runnable wrap(Runnable task) {
return task;
}
@Override
public Executor wrap(Executor delegate) {
return delegate;
}
@Override
public ExecutorService wrap(ExecutorService delegate) {
return delegate;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.deployer;
import org.springframework.cloud.sleuth.Tracer;
/**
* A noop implementation. Does nothing.
*
* @author Marcin Grzejszczak
* @since 3.0.0
*/
class NoOpSpanInScope implements Tracer.SpanInScope {
@Override
public void close() {
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.deployer;
import org.springframework.cloud.sleuth.TraceContext;
/**
* A noop implementation. Does nothing.
*
* @author Marcin Grzejszczak
* @since 3.0.0
*/
class NoOpTraceContext implements TraceContext {
@Override
public String traceId() {
return "";
}
@Override
public String parentId() {
return "";
}
@Override
public String spanId() {
return "";
}
@Override
public Boolean sampled() {
return false;
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.deployer;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.TraceContext;
/**
* A noop implementation. Does nothing.
*
* @author Marcin Grzejszczak
* @since 3.0.0
*/
class SimpleSpan implements Span {
Map<String, String> tags = new HashMap<>();
boolean started;
boolean ended;
@Override
public boolean isNoop() {
return true;
}
@Override
public TraceContext context() {
return new NoOpTraceContext();
}
@Override
public Span start() {
this.started = true;
return this;
}
@Override
public Span name(String name) {
return this;
}
@Override
public Span event(String value) {
return this;
}
@Override
public Span tag(String key, String value) {
this.tags.put(key, value);
return this;
}
@Override
public Span error(Throwable throwable) {
return this;
}
@Override
public void end() {
this.ended = true;
}
@Override
public void abandon() {
}
@Override
public Span remoteServiceName(String remoteServiceName) {
return this;
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.deployer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.assertj.core.api.BDDAssertions;
import org.springframework.cloud.sleuth.BaggageInScope;
import org.springframework.cloud.sleuth.ScopedSpan;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanCustomizer;
import org.springframework.cloud.sleuth.TraceContext;
import org.springframework.cloud.sleuth.Tracer;
/**
* A noop implementation. Does nothing.
*
* @author Marcin Grzejszczak
* @since 3.0.0
*/
class SimpleTracer implements Tracer {
List<SimpleSpan> spans = new ArrayList<>();
@Override
public Span nextSpan(Span parent) {
return new SimpleSpan();
}
SimpleSpan getOnlySpan() {
BDDAssertions.then(this.spans).hasSize(1);
SimpleSpan span = this.spans.get(0);
BDDAssertions.then(span.started).as("Span must be started").isTrue();
BDDAssertions.then(span.ended).as("Span must be finished").isTrue();
return span;
}
@Override
public SpanInScope withSpan(Span span) {
return new NoOpSpanInScope();
}
@Override
public SpanCustomizer currentSpanCustomizer() {
return null;
}
@Override
public Span currentSpan() {
return new SimpleSpan();
}
@Override
public Span nextSpan() {
final SimpleSpan span = new SimpleSpan();
this.spans.add(span);
return span;
}
@Override
public ScopedSpan startScopedSpan(String name) {
return null;
}
@Override
public Span.Builder spanBuilder() {
return null;
}
@Override
public Map<String, String> getAllBaggage() {
return new HashMap<>();
}
@Override
public BaggageInScope getBaggage(String name) {
return null;
}
@Override
public BaggageInScope getBaggage(TraceContext traceContext, String name) {
return null;
}
@Override
public BaggageInScope createBaggage(String name) {
return null;
}
@Override
public BaggageInScope createBaggage(String name, String value) {
return null;
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.deployer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.assertj.core.api.BDDAssertions;
import org.junit.jupiter.api.Test;
import org.mockito.BDDMockito;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.StaticListableBeanFactory;
import org.springframework.cloud.deployer.spi.app.AppDeployer;
import org.springframework.cloud.deployer.spi.app.AppScaleRequest;
import org.springframework.cloud.deployer.spi.app.AppStatus;
import org.springframework.cloud.deployer.spi.core.AppDefinition;
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
import org.springframework.core.env.Environment;
import org.springframework.core.io.PathResource;
import org.springframework.mock.env.MockEnvironment;
class TraceAppDeployerTests {
SimpleTracer simpleTracer = new SimpleTracer();
AppDeployer delegate = BDDMockito.mock(AppDeployer.class);
TraceAppDeployer traceAppDeployer = new TraceAppDeployer(this.delegate, beanFactory(), environment());
@Test
void should_trace_deploy() {
BDDMockito.given(this.delegate.statusReactive(BDDMockito.any()))
.willReturn(Mono.just(AppStatus.of("asd").build()));
this.traceAppDeployer.deploy(deploymentRequest());
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
BDDMockito.then(this.delegate).should().deploy(BDDMockito.any());
}
@Test
void should_trace_undeploy() {
BDDMockito.given(this.delegate.statusReactive(BDDMockito.any()))
.willReturn(Mono.just(AppStatus.of("asd").build()));
this.traceAppDeployer.undeploy("asd");
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
BDDMockito.then(this.delegate).should().undeploy(BDDMockito.any());
}
@Test
void should_trace_status() {
this.traceAppDeployer.status("asd");
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
BDDMockito.then(this.delegate).should().status(BDDMockito.any());
}
@Test
void should_trace_status_reactive() {
BDDMockito.given(this.delegate.statusReactive(BDDMockito.any()))
.willReturn(Mono.just(AppStatus.of("asd").build()));
this.traceAppDeployer.statusReactive("asd").block();
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
BDDMockito.then(this.delegate).should().statusReactive(BDDMockito.any());
}
@Test
void should_trace_statuses_reactive() {
BDDMockito.given(this.delegate.statusesReactive(BDDMockito.any()))
.willReturn(Flux.just(AppStatus.of("asd").build()));
this.traceAppDeployer.statusesReactive("asd").blockFirst();
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
BDDMockito.then(this.delegate).should().statusesReactive(BDDMockito.any());
}
@Test
void should_trace_log() {
this.traceAppDeployer.getLog("id");
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
BDDMockito.then(this.delegate).should().getLog(BDDMockito.any());
}
@Test
void should_trace_scale() {
this.traceAppDeployer.scale(new AppScaleRequest("asd", 2));
BDDAssertions.then(this.simpleTracer.getOnlySpan().tags).isNotEmpty();
BDDMockito.then(this.delegate).should().scale(BDDMockito.any());
}
private AppDeploymentRequest deploymentRequest() {
return new AppDeploymentRequest(new AppDefinition("foo", new HashMap<>()), new PathResource("/"),
deploymentProps(), commandLineArgs());
}
private List<String> commandLineArgs() {
return Arrays.asList("foo=bar1", "baz=bar2");
}
private Map<String, String> deploymentProps() {
Map<String, String> map = new HashMap<>();
map.put("deployment1", "prop1");
map.put("deployment2", "prop2");
return map;
}
private BeanFactory beanFactory() {
StaticListableBeanFactory beanFactory = new StaticListableBeanFactory();
beanFactory.addBean("tracer", this.simpleTracer);
beanFactory.addBean("currentTraceContext", new NoOpCurrentTraceContext());
return beanFactory;
}
private Environment environment() {
return new MockEnvironment();
}
}