/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.brooklyn.rest;
import static org.testng.Assert.assertEquals;
import java.io.File;
import java.net.URI;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeoutException;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.mgmt.EntityManager;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils;
import org.apache.brooklyn.entity.stock.BasicApplication;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.eclipse.jetty.server.Server;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.http.HttpTool;
import org.apache.brooklyn.util.http.HttpToolResponse;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.time.Duration;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
public class HaMasterCheckFilterTest extends BrooklynRestApiLauncherTestFixture {
private static final Duration TIMEOUT = Duration.THIRTY_SECONDS;
private File mementoDir;
private ManagementContext writeMgmt;
private ManagementContext readMgmt;
private String appId;
private Server server;
private HttpClient client;
@AfterMethod(alwaysRun=true)
public void tearDown() throws Exception {
System.err.println("TEAR DOWN");
server.stop();
Entities.destroyAll(writeMgmt);
Entities.destroyAll(readMgmt);
Os.deleteRecursively(mementoDir);
}
@Test(groups = "Integration")
public void testEntitiesExistOnDisabledHA() throws Exception {
initHaCluster(HighAvailabilityMode.DISABLED, HighAvailabilityMode.DISABLED);
assertReadIsMaster();
assertEntityExists(new ReturnCodeCheck());
}
@Test(groups = "Integration")
public void testEntitiesExistOnMasterPromotion() throws Exception {
initHaCluster(HighAvailabilityMode.AUTO, HighAvailabilityMode.AUTO);
stopWriteNode();
assertEntityExists(new ReturnCodeCheck());
assertReadIsMaster();
}
@Test(groups = "Integration")
public void testEntitiesExistOnHotStandbyAndPromotion() throws Exception {
initHaCluster(HighAvailabilityMode.AUTO, HighAvailabilityMode.HOT_STANDBY);
assertEntityExists(new ReturnCodeCheck());
stopWriteNode();
assertEntityExists(new ReturnCodeAndNodeState());
assertReadIsMaster();
}
@Test(groups = "Integration")
public void testEntitiesExistOnHotBackup() throws Exception {
initHaCluster(HighAvailabilityMode.AUTO, HighAvailabilityMode.HOT_BACKUP);
Asserts.continually(
ImmutableMap.<String,Object>of(
"timeout", Duration.THIRTY_SECONDS,
"period", Duration.ZERO),
new ReturnCodeSupplier(),
Predicates.or(Predicates.equalTo(200), Predicates.equalTo(403)));
}
private HttpClient getClient(Server server) {
HttpClient client = HttpTool.httpClientBuilder()
.uri(getBaseUri(server))
.build();
return client;
}
private int getAppResponseCode() {
HttpToolResponse response = HttpTool.httpGet(
client, URI.create(getBaseUri(server) + "/v1/applications/" + appId),
ImmutableMap.<String,String>of());
return response.getResponseCode();
}
private String createApp(ManagementContext mgmt) {
EntityManager entityMgr = mgmt.getEntityManager();
Entity app = entityMgr.createEntity(EntitySpec.create(BasicApplication.class));
entityMgr.manage(app);
return app.getId();
}
private ManagementContext createManagementContext(File mementoDir, HighAvailabilityMode mode) {
ManagementContext mgmt = RebindTestUtils.managementContextBuilder(mementoDir, getClass().getClassLoader())
.persistPeriodMillis(1)
.forLive(false)
.emptyCatalog(true)
.buildUnstarted();
if (mode == HighAvailabilityMode.DISABLED) {
mgmt.getHighAvailabilityManager().disabled();
} else {
mgmt.getHighAvailabilityManager().start(mode);
}
new BrooklynCampPlatformLauncherNoServer()
.useManagementContext(mgmt)
.launch();
return mgmt;
}
private void initHaCluster(HighAvailabilityMode writeMode, HighAvailabilityMode readMode) throws InterruptedException, TimeoutException {
mementoDir = Os.newTempDir(getClass());
writeMgmt = createManagementContext(mementoDir, writeMode);
appId = createApp(writeMgmt);
writeMgmt.getRebindManager().waitForPendingComplete(TIMEOUT, true);
if (readMode == HighAvailabilityMode.DISABLED) {
//no HA, one node only
readMgmt = writeMgmt;
} else {
readMgmt = createManagementContext(mementoDir, readMode);
}
server = useServerForTest(BrooklynRestApiLauncher.launcher()
.managementContext(readMgmt)
.securityProvider(AnyoneSecurityProvider.class)
.forceUseOfDefaultCatalogWithJavaClassPath(true)
.withoutJsgui()
.disableHighAvailability(false)
.start());
client = getClient(server);
}
private void assertEntityExists(Callable<Integer> c) {
assertEquals((int)Asserts.succeedsEventually(c), 200);
}
private void assertReadIsMaster() {
assertEquals(readMgmt.getHighAvailabilityManager().getNodeState(), ManagementNodeState.MASTER);
}
private void stopWriteNode() {
writeMgmt.getHighAvailabilityManager().stop();
}
private class ReturnCodeCheck implements Callable<Integer> {
@Override
public Integer call() {
int retCode = getAppResponseCode();
if (retCode == 403) {
throw new RuntimeException("Not ready, retry. Response - " + retCode);
} else {
return retCode;
}
}
}
private class ReturnCodeAndNodeState extends ReturnCodeCheck {
@Override
public Integer call() {
Integer ret = super.call();
if (ret == HttpStatus.SC_OK) {
ManagementNodeState state = readMgmt.getHighAvailabilityManager().getNodeState();
if (state != ManagementNodeState.MASTER) {
throw new RuntimeException("Not master yet " + state);
}
}
return ret;
}
}
private class ReturnCodeSupplier implements Supplier<Integer> {
@Override
public Integer get() {
return getAppResponseCode();
}
}
}