/**
* Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG
* 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 http://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 com.sixt.service.framework.registry.consul;
import com.sixt.service.framework.ServiceProperties;
import com.sixt.service.framework.rpc.LoadBalancer;
import com.sixt.service.framework.rpc.LoadBalancerUpdate;
import com.sixt.service.framework.util.Sleeper;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpFields;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.List;
import static com.sixt.service.framework.registry.consul.RegistrationMonitorWorker.CONSUL_INDEX;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
public class RegistrationMonitorWorkerIntegrationTest {
private RegistrationMonitorWorker worker;
private ServiceProperties props;
private ContentResponse response;
private String healthInfo = "[{\"Checks\":[{\"Status\":\"passing\"}],\"Service\":{\"ID\":\"xxx\",\"" +
"Port\":2,\"Address\":\"1.1.1.1\"}}]";
private String emptyHealthInfo = "[]";
@Before
public void setup() throws Exception {
HttpClient httpClient = mock(HttpClient.class);
response = mock(ContentResponse.class);
when(response.getStatus()).thenReturn(200);
when(response.getContentAsString()).thenReturn(healthInfo);
HttpFields headers = new HttpFields();
headers.add(CONSUL_INDEX, "42");
when(response.getHeaders()).thenReturn(headers);
Request request = mock(Request.class);
when(httpClient.newRequest(anyString())).thenReturn(request);
when(request.send()).thenReturn(response);
props = new ServiceProperties();
props.addProperty(ServiceProperties.REGISTRY_SERVER_KEY, "localhost:1234");
worker = new RegistrationMonitorWorker(httpClient, props);
worker.setServiceName("foobar");
}
@Test
public void getRegistrationUris() {
props.addProperty(ServiceProperties.REGISTRY_SERVER_KEY, "localhost:1234");
assertThat(worker.getServiceHealthUri().toString()).isEqualTo(
"http://localhost:1234/v1/health/service/foobar?stale");
worker.consulIndex = "69";
assertThat(worker.getServiceHealthUri().toString()).isEqualTo(
"http://localhost:1234/v1/health/service/foobar?stale&index=69");
}
@Test(expected = IllegalStateException.class)
public void noServiceNameSet() {
worker.setServiceName(null);
worker.run();
}
@Test
public void requestHealthListSingleInstance() {
when(response.getContentAsString()).thenReturn(healthInfo);
List<ConsulHealthEntry> entries = worker.loadCurrentHealthList();
assertThat(entries.size()).isEqualTo(1);
}
@Test
public void problemGettingHealthInfo() {
when(response.getStatus()).thenReturn(404);
List<ConsulHealthEntry> entries = worker.loadCurrentHealthList();
assertThat(entries).isEmpty();
when(response.getStatus()).thenReturn(0);
entries = worker.loadCurrentHealthList();
assertThat(entries).isEmpty();
}
@Test
public void initialServiceReported() {
List<ConsulHealthEntry> list = new ArrayList<>();
ConsulHealthEntry entry = new ConsulHealthEntry("1", ConsulHealthEntry.Status.Passing, "1.1.1.1", 1);
list.add(entry);
LoadBalancer lb = mock(LoadBalancer.class);
worker.setLoadbalancer(lb);
worker.reportInitialServicesList(list);
assertThat(worker.discoveredServices).hasSize(1);
verify(lb, times(1)).updateServiceEndpoints(any(LoadBalancerUpdate.class));
}
@Test
public void serviceAppearsAfterStartup() throws Exception {
//there was a bug where if a consumed service wasn't available at startup,
//even when it came online later, it wouldn't be tracked.
String emptyHealthInfo = "[]";
when(response.getContentAsString()).thenReturn(emptyHealthInfo).
thenReturn(healthInfo);
LoadBalancer lb = mock(LoadBalancer.class);
worker.setLoadbalancer(lb);
worker.shutdownSemaphore.release();
worker.sleeper = mock(Sleeper.class);
worker.run();
ArgumentCaptor<LoadBalancerUpdate> captor = ArgumentCaptor.forClass(LoadBalancerUpdate.class);
verify(lb, times(1)).updateServiceEndpoints(captor.capture());
assertThat(captor.getAllValues()).hasSize(1);
LoadBalancerUpdate secondUpdate = captor.getAllValues().get(0);
assertThat(secondUpdate.getNewServices()).isNotEmpty();
}
@Test
public void serviceFlappingAfterStartup() throws Exception {
//there was a bug where if a consumed service went down and came back, there
//were extra and incorrect updates posted to the lb
//1. is available 2. becomes unavail 3. becomes avail again
when(response.getContentAsString()).thenReturn(healthInfo).thenReturn(emptyHealthInfo).
then((Answer<String>) invocation -> {
worker.shutdownSemaphore.release();
return healthInfo;
});
LoadBalancer lb = mock(LoadBalancer.class);
worker.setLoadbalancer(lb);
worker.sleeper = mock(Sleeper.class);
worker.run();
ArgumentCaptor<LoadBalancerUpdate> captor = ArgumentCaptor.forClass(LoadBalancerUpdate.class);
verify(lb, times(3)).updateServiceEndpoints(captor.capture());
assertThat(captor.getAllValues()).hasSize(3);
}
// @Test
// public void newGoInstancesStartUnhealthy() throws UnsupportedEncodingException {
// //TODO: determine if this is a bug with the go services are registering,
// // or if it is inherent in the consul registration process and
// // deprecate the workaround (or give a warning)
//
// StringEntity firstServiceInfo = new StringEntity("[{\"ServiceID\":\"xxx\"," +
// "\"ServiceAddress\":\"1.1.1.1\",\"ServicePort\":2,\"ModifyIndex\":3}]");
// StringEntity secondServiceInfo = new StringEntity("[{\"ServiceID\":\"xyz\"," +
// "\"ServiceAddress\":\"1.1.1.1\",\"ServicePort\":3,\"ModifyIndex\":3}]");
// StringEntity emptyHealthInfo = new StringEntity("[]");
// StringEntity firstHealthInfo = new StringEntity("[{\"ServiceID\":\"xxx\",\"" +
// "Status\":\"passing\"}]");
// StringEntity secondUnhealhy = new StringEntity("[{\"ServiceID\":\"xyz\",\"" +
// "Status\":\"critical\"}]");
// StringEntity secondHealthy = new StringEntity("[{\"ServiceID\":\"xyz\",\"" +
// "Status\":\"passing\"}]");
// when(response.getEntity()).thenReturn(firstServiceInfo).thenReturn(firstHealthInfo).
// thenReturn(firstHealthInfo).thenReturn(firstHealthInfo).thenReturn(emptyHealthInfo).
// thenReturn(secondUnhealhy).thenReturn(secondServiceInfo).
// thenReturn(secondHealthy).
// then((Answer<StringEntity>) invocation -> {
// worker.shutdownSemaphore.release();
// return secondHealthy;
// });
//
// LoadBalancer lb = mock(LoadBalancer.class);
// worker.setLoadbalancer(lb);
// //worker.shutdownSemaphore.release();
// worker.sleeper = mock(Sleeper.class);
//
// worker.run();
// ArgumentCaptor<LoadBalancerUpdate> captor = ArgumentCaptor.forClass(LoadBalancerUpdate.class);
// verify(lb, atLeast(3)).updateServiceEndpoints(captor.capture()); //should be only 3
// List<LoadBalancerUpdate> sentUpdates = captor.getAllValues();
// assertThat(sentUpdates).hasSize(3);
// }
@Test
public void verifyMainLoop() {
LoadBalancer lb = mock(LoadBalancer.class);
worker.setLoadbalancer(lb);
worker.shutdownSemaphore.release();
worker.sleeper = mock(Sleeper.class);
when(response.getContentAsString()).thenReturn(null).thenReturn(healthInfo).thenReturn(healthInfo);
worker.run();
verify(lb, times(1)).updateServiceEndpoints(any(LoadBalancerUpdate.class));
}
}