use of io.grpc.LoadBalancer.SubchannelStateListener in project grpc-java by grpc.
the class ManagedChannelImplTest method subchannels.
@Test
public void subchannels() {
createChannel();
// createSubchannel() always return a new Subchannel
Attributes attrs1 = Attributes.newBuilder().set(SUBCHANNEL_ATTR_KEY, "attr1").build();
Attributes attrs2 = Attributes.newBuilder().set(SUBCHANNEL_ATTR_KEY, "attr2").build();
SubchannelStateListener listener1 = mock(SubchannelStateListener.class);
SubchannelStateListener listener2 = mock(SubchannelStateListener.class);
final Subchannel sub1 = createSubchannelSafely(helper, addressGroup, attrs1, listener1);
final Subchannel sub2 = createSubchannelSafely(helper, addressGroup, attrs2, listener2);
assertNotSame(sub1, sub2);
assertNotSame(attrs1, attrs2);
assertSame(attrs1, sub1.getAttributes());
assertSame(attrs2, sub2.getAttributes());
final AtomicBoolean snippetPassed = new AtomicBoolean(false);
helper.getSynchronizationContext().execute(new Runnable() {
@Override
public void run() {
// getAddresses() must be called from sync context
assertSame(addressGroup, sub1.getAddresses());
assertSame(addressGroup, sub2.getAddresses());
snippetPassed.set(true);
}
});
assertThat(snippetPassed.get()).isTrue();
// requestConnection()
verify(mockTransportFactory, never()).newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class), any(TransportLogger.class));
requestConnectionSafely(helper, sub1);
verify(mockTransportFactory).newClientTransport(eq(socketAddress), eq(clientTransportOptions), isA(TransportLogger.class));
MockClientTransportInfo transportInfo1 = transports.poll();
assertNotNull(transportInfo1);
requestConnectionSafely(helper, sub2);
verify(mockTransportFactory, times(2)).newClientTransport(eq(socketAddress), eq(clientTransportOptions), isA(TransportLogger.class));
MockClientTransportInfo transportInfo2 = transports.poll();
assertNotNull(transportInfo2);
requestConnectionSafely(helper, sub1);
requestConnectionSafely(helper, sub2);
// The subchannel doesn't matter since this isn't called
verify(mockTransportFactory, times(2)).newClientTransport(eq(socketAddress), eq(clientTransportOptions), isA(TransportLogger.class));
// updateAddresses()
updateAddressesSafely(helper, sub1, Collections.singletonList(addressGroup2));
assertThat(((InternalSubchannel) sub1.getInternalSubchannel()).getAddressGroups()).isEqualTo(Collections.singletonList(addressGroup2));
// shutdown() has a delay
shutdownSafely(helper, sub1);
timer.forwardTime(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS - 1, TimeUnit.SECONDS);
shutdownSafely(helper, sub1);
verify(transportInfo1.transport, never()).shutdown(any(Status.class));
timer.forwardTime(1, TimeUnit.SECONDS);
verify(transportInfo1.transport).shutdown(same(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_STATUS));
// ... but not after Channel is terminating
verify(mockLoadBalancer, never()).shutdown();
channel.shutdown();
verify(mockLoadBalancer).shutdown();
verify(transportInfo2.transport, never()).shutdown(any(Status.class));
shutdownSafely(helper, sub2);
verify(transportInfo2.transport).shutdown(same(ManagedChannelImpl.SHUTDOWN_STATUS));
// Cleanup
transportInfo1.listener.transportShutdown(Status.UNAVAILABLE);
transportInfo1.listener.transportTerminated();
transportInfo2.listener.transportShutdown(Status.UNAVAILABLE);
transportInfo2.listener.transportTerminated();
timer.forwardTime(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS);
}
use of io.grpc.LoadBalancer.SubchannelStateListener in project grpc-java by grpc.
the class ManagedChannelImplTest method noMoreCallbackAfterLoadBalancerShutdown.
@Test
public void noMoreCallbackAfterLoadBalancerShutdown() {
FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory.Builder(expectedUri).setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))).build();
channelBuilder.nameResolverFactory(nameResolverFactory);
Status resolutionError = Status.UNAVAILABLE.withDescription("Resolution failed");
createChannel();
FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0);
verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class));
verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture());
assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup);
SubchannelStateListener stateListener1 = mock(SubchannelStateListener.class);
SubchannelStateListener stateListener2 = mock(SubchannelStateListener.class);
Subchannel subchannel1 = createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, stateListener1);
Subchannel subchannel2 = createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, stateListener2);
requestConnectionSafely(helper, subchannel1);
requestConnectionSafely(helper, subchannel2);
verify(mockTransportFactory, times(2)).newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class));
MockClientTransportInfo transportInfo1 = transports.poll();
MockClientTransportInfo transportInfo2 = transports.poll();
// LoadBalancer receives all sorts of callbacks
transportInfo1.listener.transportReady();
verify(stateListener1, times(2)).onSubchannelState(stateInfoCaptor.capture());
assertSame(CONNECTING, stateInfoCaptor.getAllValues().get(0).getState());
assertSame(READY, stateInfoCaptor.getAllValues().get(1).getState());
verify(stateListener2).onSubchannelState(stateInfoCaptor.capture());
assertSame(CONNECTING, stateInfoCaptor.getValue().getState());
resolver.listener.onError(resolutionError);
verify(mockLoadBalancer).handleNameResolutionError(resolutionError);
verifyNoMoreInteractions(mockLoadBalancer);
channel.shutdown();
verify(mockLoadBalancer).shutdown();
verifyNoMoreInteractions(stateListener1, stateListener2);
// LoadBalancer will normally shutdown all subchannels
shutdownSafely(helper, subchannel1);
shutdownSafely(helper, subchannel2);
// Since subchannels are shutdown, SubchannelStateListeners will only get SHUTDOWN regardless of
// the transport states.
transportInfo1.listener.transportShutdown(Status.UNAVAILABLE);
transportInfo2.listener.transportReady();
verify(stateListener1).onSubchannelState(ConnectivityStateInfo.forNonError(SHUTDOWN));
verify(stateListener2).onSubchannelState(ConnectivityStateInfo.forNonError(SHUTDOWN));
verifyNoMoreInteractions(stateListener1, stateListener2);
// No more callback should be delivered to LoadBalancer after it's shut down
resolver.listener.onError(resolutionError);
resolver.resolved();
verifyNoMoreInteractions(mockLoadBalancer);
}
use of io.grpc.LoadBalancer.SubchannelStateListener in project grpc-java by grpc.
the class HealthCheckingLoadBalancerFactoryTest method serverRespondResetsBackoff.
@Test
public void serverRespondResetsBackoff() {
Attributes resolutionAttrs = attrsWithHealthCheckService("TeeService");
ResolvedAddresses result = ResolvedAddresses.newBuilder().setAddresses(resolvedAddressList).setAttributes(resolutionAttrs).build();
hcLbEventDelivery.handleResolvedAddresses(result);
verify(origLb).handleResolvedAddresses(result);
verifyNoMoreInteractions(origLb);
SubchannelStateListener mockStateListener = mockStateListeners[0];
Subchannel subchannel = createSubchannel(0, Attributes.EMPTY);
assertThat(unwrap(subchannel)).isSameInstanceAs(subchannels[0]);
InOrder inOrder = inOrder(mockStateListener, backoffPolicyProvider, backoffPolicy1, backoffPolicy2);
deliverSubchannelState(0, ConnectivityStateInfo.forNonError(READY));
inOrder.verify(mockStateListener).onSubchannelState(eq(ConnectivityStateInfo.forNonError(CONNECTING)));
HealthImpl healthImpl = healthImpls[0];
assertThat(healthImpl.calls).hasSize(1);
assertThat(clock.getPendingTasks()).isEmpty();
// Server closes the health checking RPC without any response
healthImpl.calls.poll().responseObserver.onError(Status.CANCELLED.asException());
// which results in TRANSIENT_FAILURE
inOrder.verify(mockStateListener).onSubchannelState(unavailableStateWithMsg("Health-check stream unexpectedly closed with " + Status.CANCELLED + " for 'TeeService'"));
// Retry with backoff is scheduled
inOrder.verify(backoffPolicyProvider).get();
inOrder.verify(backoffPolicy1).nextBackoffNanos();
assertThat(clock.getPendingTasks()).hasSize(1);
verifyRetryAfterNanos(inOrder, mockStateListener, healthImpl, 11);
assertThat(clock.getPendingTasks()).isEmpty();
// Server responds
healthImpl.calls.peek().responseObserver.onNext(makeResponse(ServingStatus.SERVING));
inOrder.verify(mockStateListener).onSubchannelState(eq(ConnectivityStateInfo.forNonError(READY)));
verifyNoMoreInteractions(mockStateListener);
// then closes the stream
healthImpl.calls.poll().responseObserver.onError(Status.UNAVAILABLE.asException());
inOrder.verify(mockStateListener).onSubchannelState(unavailableStateWithMsg("Health-check stream unexpectedly closed with " + Status.UNAVAILABLE + " for 'TeeService'"));
// Because server has responded, the first retry is not subject to backoff.
// But the backoff policy has been reset. A new backoff policy will be used for
// the next backed-off retry.
inOrder.verify(mockStateListener).onSubchannelState(eq(ConnectivityStateInfo.forNonError(CONNECTING)));
assertThat(healthImpl.calls).hasSize(1);
assertThat(clock.getPendingTasks()).isEmpty();
inOrder.verifyNoMoreInteractions();
// then closes the stream for this retry
healthImpl.calls.poll().responseObserver.onError(Status.UNAVAILABLE.asException());
inOrder.verify(mockStateListener).onSubchannelState(unavailableStateWithMsg("Health-check stream unexpectedly closed with " + Status.UNAVAILABLE + " for 'TeeService'"));
// New backoff policy is used
inOrder.verify(backoffPolicyProvider).get();
// Retry with a new backoff policy
inOrder.verify(backoffPolicy2).nextBackoffNanos();
verifyRetryAfterNanos(inOrder, mockStateListener, healthImpl, 12);
}
use of io.grpc.LoadBalancer.SubchannelStateListener in project grpc-java by grpc.
the class HealthCheckingLoadBalancerFactoryTest method typicalWorkflow.
@Test
public void typicalWorkflow() {
Attributes resolutionAttrs = attrsWithHealthCheckService("FooService");
ResolvedAddresses result = ResolvedAddresses.newBuilder().setAddresses(resolvedAddressList).setAttributes(resolutionAttrs).build();
hcLbEventDelivery.handleResolvedAddresses(result);
verify(origLb).handleResolvedAddresses(result);
verify(origHelper, atLeast(0)).getSynchronizationContext();
verify(origHelper, atLeast(0)).getScheduledExecutorService();
verifyNoMoreInteractions(origHelper);
verifyNoMoreInteractions(origLb);
Subchannel[] wrappedSubchannels = new Subchannel[NUM_SUBCHANNELS];
// Simulate that the orignal LB creates Subchannels
for (int i = 0; i < NUM_SUBCHANNELS; i++) {
// Subchannel attributes set by origLb are correctly plumbed in
String subchannelAttrValue = "eag attr " + i;
Attributes attrs = Attributes.newBuilder().set(SUBCHANNEL_ATTR_KEY, subchannelAttrValue).build();
wrappedSubchannels[i] = createSubchannel(i, attrs);
assertThat(unwrap(wrappedSubchannels[i])).isSameInstanceAs(subchannels[i]);
verify(origHelper, times(i + 1)).createSubchannel(createArgsCaptor.capture());
assertThat(createArgsCaptor.getValue().getAddresses()).isEqualTo(eagLists[i]);
assertThat(createArgsCaptor.getValue().getAttributes().get(SUBCHANNEL_ATTR_KEY)).isEqualTo(subchannelAttrValue);
}
for (int i = NUM_SUBCHANNELS - 1; i >= 0; i--) {
// Not starting health check until underlying Subchannel is READY
FakeSubchannel subchannel = subchannels[i];
HealthImpl healthImpl = healthImpls[i];
SubchannelStateListener mockStateListener = mockStateListeners[i];
InOrder inOrder = inOrder(mockStateListener);
deliverSubchannelState(i, ConnectivityStateInfo.forNonError(CONNECTING));
deliverSubchannelState(i, ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE));
deliverSubchannelState(i, ConnectivityStateInfo.forNonError(IDLE));
inOrder.verify(mockStateListener).onSubchannelState(eq(ConnectivityStateInfo.forNonError(CONNECTING)));
inOrder.verify(mockStateListener).onSubchannelState(eq(ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE)));
inOrder.verify(mockStateListener).onSubchannelState(eq(ConnectivityStateInfo.forNonError(IDLE)));
verifyNoMoreInteractions(mockStateListener);
assertThat(subchannel.logs).isEmpty();
assertThat(healthImpl.calls).isEmpty();
deliverSubchannelState(i, ConnectivityStateInfo.forNonError(READY));
assertThat(healthImpl.calls).hasSize(1);
ServerSideCall serverCall = healthImpl.calls.peek();
assertThat(serverCall.request).isEqualTo(makeRequest("FooService"));
// Starting the health check will make the Subchannel appear CONNECTING to the origLb.
inOrder.verify(mockStateListener).onSubchannelState(eq(ConnectivityStateInfo.forNonError(CONNECTING)));
verifyNoMoreInteractions(mockStateListener);
assertThat(subchannel.logs).containsExactly("INFO: CONNECTING: Starting health-check for \"FooService\"");
subchannel.logs.clear();
// Simulate a series of responses.
for (ServingStatus servingStatus : new ServingStatus[] { ServingStatus.UNKNOWN, ServingStatus.NOT_SERVING, ServingStatus.SERVICE_UNKNOWN, ServingStatus.SERVING, ServingStatus.NOT_SERVING, ServingStatus.SERVING }) {
serverCall.responseObserver.onNext(makeResponse(servingStatus));
// SERVING is mapped to READY, while other statuses are mapped to TRANSIENT_FAILURE
if (servingStatus == ServingStatus.SERVING) {
inOrder.verify(mockStateListener).onSubchannelState(eq(ConnectivityStateInfo.forNonError(READY)));
assertThat(subchannel.logs).containsExactly("INFO: READY: health-check responded SERVING");
} else {
inOrder.verify(mockStateListener).onSubchannelState(unavailableStateWithMsg("Health-check service responded " + servingStatus + " for 'FooService'"));
assertThat(subchannel.logs).containsExactly("INFO: TRANSIENT_FAILURE: health-check responded " + servingStatus);
}
subchannel.logs.clear();
verifyNoMoreInteractions(mockStateListener);
}
}
// origLb shuts down Subchannels
for (int i = 0; i < NUM_SUBCHANNELS; i++) {
FakeSubchannel subchannel = subchannels[i];
SubchannelStateListener mockStateListener = mockStateListeners[i];
ServerSideCall serverCall = healthImpls[i].calls.peek();
assertThat(serverCall.cancelled).isFalse();
verifyNoMoreInteractions(mockStateListener);
assertThat(subchannels[i].isShutdown).isFalse();
final Subchannel wrappedSubchannel = wrappedSubchannels[i];
// Subchannel enters SHUTDOWN state as a response to shutdown(), and that will cancel the
// health check RPC
syncContext.execute(new Runnable() {
@Override
public void run() {
wrappedSubchannel.shutdown();
}
});
assertThat(subchannels[i].isShutdown).isTrue();
assertThat(serverCall.cancelled).isTrue();
verify(mockStateListener).onSubchannelState(eq(ConnectivityStateInfo.forNonError(SHUTDOWN)));
assertThat(subchannel.logs).isEmpty();
}
for (int i = 0; i < NUM_SUBCHANNELS; i++) {
assertThat(healthImpls[i].calls).hasSize(1);
}
verifyNoInteractions(backoffPolicyProvider);
}
use of io.grpc.LoadBalancer.SubchannelStateListener in project grpc-java by grpc.
the class HealthCheckingLoadBalancerFactoryTest method serviceConfigChangesServiceNameWhenRetryPending.
@Test
public void serviceConfigChangesServiceNameWhenRetryPending() {
Attributes resolutionAttrs = attrsWithHealthCheckService("TeeService");
ResolvedAddresses result1 = ResolvedAddresses.newBuilder().setAddresses(resolvedAddressList).setAttributes(resolutionAttrs).build();
hcLbEventDelivery.handleResolvedAddresses(result1);
verify(origLb).handleResolvedAddresses(result1);
verifyNoMoreInteractions(origLb);
Subchannel subchannel = createSubchannel(0, Attributes.EMPTY);
SubchannelStateListener mockListener = mockStateListeners[0];
assertThat(unwrap(subchannel)).isSameInstanceAs(subchannels[0]);
InOrder inOrder = inOrder(origLb, mockListener);
deliverSubchannelState(0, ConnectivityStateInfo.forNonError(READY));
inOrder.verify(mockListener).onSubchannelState(eq(ConnectivityStateInfo.forNonError(CONNECTING)));
HealthImpl healthImpl = healthImpls[0];
assertThat(healthImpl.calls).hasSize(1);
ServerSideCall serverCall = healthImpl.calls.poll();
assertThat(serverCall.cancelled).isFalse();
assertThat(serverCall.request).isEqualTo(makeRequest("TeeService"));
// Health check stream closed without responding. Client in retry backoff.
assertThat(clock.getPendingTasks()).isEmpty();
serverCall.responseObserver.onCompleted();
assertThat(clock.getPendingTasks()).hasSize(1);
assertThat(healthImpl.calls).isEmpty();
inOrder.verify(mockListener).onSubchannelState(unavailableStateWithMsg("Health-check stream unexpectedly closed with " + Status.OK + " for 'TeeService'"));
// Service config returns with the same health check name.
hcLbEventDelivery.handleResolvedAddresses(result1);
// It's delivered to origLb, but nothing else happens
inOrder.verify(origLb).handleResolvedAddresses(result1);
verifyNoMoreInteractions(origLb, mockListener);
assertThat(clock.getPendingTasks()).hasSize(1);
assertThat(healthImpl.calls).isEmpty();
// Service config returns a different health check name.
resolutionAttrs = attrsWithHealthCheckService("FooService");
ResolvedAddresses result2 = ResolvedAddresses.newBuilder().setAddresses(resolvedAddressList).setAttributes(resolutionAttrs).build();
hcLbEventDelivery.handleResolvedAddresses(result2);
// Concluded CONNECTING state
inOrder.verify(mockListener).onSubchannelState(eq(ConnectivityStateInfo.forNonError(CONNECTING)));
inOrder.verify(origLb).handleResolvedAddresses(result2);
// Current retry timer cancelled
assertThat(clock.getPendingTasks()).isEmpty();
// A second RPC is started immediately
assertThat(healthImpl.calls).hasSize(1);
serverCall = healthImpl.calls.poll();
// with the new service name
assertThat(serverCall.request).isEqualTo(makeRequest("FooService"));
verifyNoMoreInteractions(origLb, mockListener);
}
Aggregations