/*
* 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.sling.discovery.base.its.setup;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.Servlet;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.discovery.InstanceDescription;
import org.apache.sling.discovery.PropertyProvider;
import org.apache.sling.discovery.TopologyEventListener;
import org.apache.sling.discovery.base.commons.BaseDiscoveryService;
import org.apache.sling.discovery.base.commons.ClusterViewService;
import org.apache.sling.discovery.base.commons.UndefinedClusterViewException;
import org.apache.sling.discovery.base.commons.ViewChecker;
import org.apache.sling.discovery.base.connectors.announcement.AnnouncementRegistry;
import org.apache.sling.discovery.base.connectors.ping.ConnectorRegistry;
import org.apache.sling.discovery.base.connectors.ping.TopologyConnectorClientInformation;
import org.apache.sling.discovery.base.connectors.ping.TopologyConnectorServlet;
import org.apache.sling.discovery.base.its.setup.mock.ArtificialDelay;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.osgi.framework.Constants;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import junitx.util.PrivateAccessor;
public class VirtualInstance {
protected final static Logger logger = LoggerFactory.getLogger(VirtualInstance.class);
public final String slingId;
ClusterViewService clusterViewService;
private final ResourceResolverFactory resourceResolverFactory;
private final OSGiMock osgiMock;
private final BaseDiscoveryService discoveryService;
private final AnnouncementRegistry announcementRegistry;
private final ConnectorRegistry connectorRegistry;
protected final String debugName;
private ResourceResolver resourceResolver;
private int serviceId = 999;
private ViewCheckerRunner viewCheckerRunner = null;
private ServletContextHandler servletContext;
private Server jettyServer;
private ModifiableTestBaseConfig config;
private ViewChecker viewChecker;
private final VirtualInstanceBuilder builder;
private final ArtificialDelay delay;
private class ViewCheckerRunner implements Runnable {
private final int intervalInSeconds;
private boolean stopping_ = false;
private volatile boolean stopped_ = false;
public ViewCheckerRunner(int intervalInSeconds) {
this.intervalInSeconds = intervalInSeconds;
}
public synchronized void stop() {
logger.info("Stopping Instance ["+slingId+"]");
stopping_ = true;
this.notifyAll();
}
public boolean hasStopped() {
return stopped_;
}
@Override
public void run() {
try{
doRun();
} finally {
stopped_ = true;
logger.info("Instance ["+slingId+"] stopped.");
}
}
public void doRun() {
while(true) {
synchronized(this) {
if (stopping_) {
logger.info("Instance ["+slingId+"] stopps.");
return;
}
}
try{
heartbeatsAndCheckView();
} catch(Exception e) {
logger.error("run: ping connector for slingId="+slingId+" threw exception: "+e, e);
}
synchronized(this) {
try {
this.wait(intervalInSeconds*1000);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}
}
}
public VirtualInstance(VirtualInstanceBuilder builder) throws Exception {
this.builder = builder;
this.slingId = builder.getSlingId();
this.debugName = builder.getDebugName();
this.delay = builder.getDelay();
logger.info("<init>: starting slingId="+slingId+", debugName="+debugName);
osgiMock = new OSGiMock();
this.resourceResolverFactory = builder.getResourceResolverFactory();
config = builder.getConnectorConfig();
config.addTopologyConnectorWhitelistEntry("127.0.0.1");
config.setMinEventDelay(builder.getMinEventDelay());
clusterViewService = builder.getClusterViewService();
announcementRegistry = builder.getAnnouncementRegistry();
connectorRegistry = builder.getConnectorRegistry();
viewChecker = builder.getViewChecker();
discoveryService = builder.getDiscoverService();
osgiMock.addService(clusterViewService);
osgiMock.addService(announcementRegistry);
osgiMock.addService(connectorRegistry);
osgiMock.addService(viewChecker);
osgiMock.addService(discoveryService);
osgiMock.addServices(builder.getAdditionalServices(this));
resourceResolver = resourceResolverFactory
.getServiceResourceResolver(null);
if (builder.isResetRepo()) {
//SLING-4587 : do resetRepo before creating the observationListener
// otherwise it will get tons of events from the deletion of /var
// which the previous test could have left over.
// Doing it before addEventListener should prevent that.
builder.resetRepo();
}
osgiMock.activateAll();
}
public void setDelay(String operationDescriptor, long delayMillis) {
delay.setDelay(operationDescriptor, delayMillis);
}
@Override
public String toString() {
return "a [Test]Instance[slingId="+slingId+", debugName="+debugName+"]";
}
public void bindPropertyProvider(PropertyProvider propertyProvider,
String... propertyNames) throws Throwable {
Map<String, Object> props = new HashMap<String, Object>();
props.put(Constants.SERVICE_ID, (long) serviceId++);
props.put(PropertyProvider.PROPERTY_PROPERTIES, propertyNames);
PrivateAccessor.invoke(discoveryService, "bindPropertyProvider",
new Class[] { PropertyProvider.class, Map.class },
new Object[] { propertyProvider, props });
}
public String getSlingId() {
return slingId;
}
public ClusterViewService getClusterViewService() {
return clusterViewService;
}
public BaseDiscoveryService getDiscoveryService() {
return discoveryService;
}
public AnnouncementRegistry getAnnouncementRegistry() {
return announcementRegistry;
}
public synchronized void startJetty() throws Throwable {
if (jettyServer!=null) {
return;
}
servletContext = new ServletContextHandler(ServletContextHandler.NO_SECURITY);
servletContext.setContextPath("/");
TopologyConnectorServlet servlet = new TopologyConnectorServlet();
PrivateAccessor.setField(servlet, "config", config);
PrivateAccessor.setField(servlet, "clusterViewService", clusterViewService);
PrivateAccessor.setField(servlet, "announcementRegistry", announcementRegistry);
Mockery context = new JUnit4Mockery();
final HttpService httpService = context.mock(HttpService.class);
context.checking(new Expectations() {
{
allowing(httpService).registerServlet(with(any(String.class)),
with(any(Servlet.class)),
with(any(Dictionary.class)),
with(any(HttpContext.class)));
}
});
PrivateAccessor.setField(servlet, "httpService", httpService);
ComponentContext cc = null;
PrivateAccessor.invoke(servlet, "activate", new Class[] {ComponentContext.class}, new Object[] {cc});
ServletHolder holder =
new ServletHolder(servlet);
servletContext.addServlet(holder, "/system/console/topology/*");
jettyServer = new Server();
jettyServer.setHandler(servletContext);
Connector connector=new SelectChannelConnector();
jettyServer.setConnectors(new Connector[]{connector});
jettyServer.start();
}
public synchronized int getJettyPort() {
if (jettyServer==null) {
throw new IllegalStateException("jettyServer not started");
}
final Connector[] connectors = jettyServer.getConnectors();
return connectors[0].getLocalPort();
}
public TopologyConnectorClientInformation connectTo(String url) throws MalformedURLException {
return connectorRegistry.registerOutgoingConnector(clusterViewService, new URL(url));
}
public InstanceDescription getLocalInstanceDescription() throws UndefinedClusterViewException {
final Iterator<InstanceDescription> it = getClusterViewService().getLocalClusterView().getInstances().iterator();
while(it.hasNext()) {
final InstanceDescription id = it.next();
if (slingId.equals(id.getSlingId())) {
return id;
}
}
fail("no local instanceDescription found");
// never called:
return null;
}
public void heartbeatsAndCheckView() {
logger.info("Instance ["+slingId+"] issues a pulse now "+new Date());
viewChecker.heartbeatAndCheckView();
}
public void startViewChecker(int intervalInSeconds) throws IllegalAccessException, InvocationTargetException {
logger.info("startViewChecker: intervalInSeconds="+intervalInSeconds);
if (viewCheckerRunner!=null) {
logger.info("startViewChecker: stopping first...");
viewCheckerRunner.stop();
logger.info("startViewChecker: stopped.");
}
logger.info("startViewChecker: activating...");
try{
OSGiMock.activate(viewChecker);
} catch(Error er) {
er.printStackTrace(System.out);
throw er;
} catch(RuntimeException re) {
re.printStackTrace(System.out);
}
logger.info("startViewChecker: initializing...");
viewCheckerRunner = new ViewCheckerRunner(intervalInSeconds);
Thread th = new Thread(viewCheckerRunner, "Test-ViewCheckerRunner ["+debugName+"]");
th.setDaemon(true);
logger.info("startViewChecker: starting thread...");
th.start();
logger.info("startViewChecker: done.");
}
public boolean isViewCheckerRunning() {
return (viewCheckerRunner!=null);
}
public void stopViewChecker() throws Throwable {
if (viewCheckerRunner!=null) {
viewCheckerRunner.stop();
while(!viewCheckerRunner.hasStopped()) {
logger.info("stopViewChecker: ["+getDebugName()+"] waiting for viewCheckerRunner to stop");
Thread.sleep(500);
}
logger.info("stopViewChecker: ["+getDebugName()+"] viewCheckerRunner stopped");
viewCheckerRunner = null;
}
try{
OSGiMock.deactivate(viewChecker);
} catch(Error er) {
er.printStackTrace(System.out);
throw er;
} catch(RuntimeException re) {
re.printStackTrace(System.out);
throw re;
}
}
public void dumpRepo() throws Exception {
VirtualInstanceHelper.dumpRepo(resourceResolverFactory);
}
public ResourceResolverFactory getResourceResolverFactory() {
return resourceResolverFactory;
}
public void stop() throws Exception {
logger.info("stop: stopping slingId="+slingId+", debugName="+debugName);
try {
stopViewChecker();
} catch (Throwable e) {
throw new Exception("Caught Throwable in stop(): "+e, e);
}
if (resourceResolver != null) {
resourceResolver.close();
}
osgiMock.deactivateAll();
logger.info("stop: stopped slingId="+slingId+", debugName="+debugName);
}
public void bindTopologyEventListener(TopologyEventListener eventListener)
throws Throwable {
PrivateAccessor.invoke(discoveryService, "bindTopologyEventListener",
new Class[] { TopologyEventListener.class },
new Object[] { eventListener });
}
public ModifiableTestBaseConfig getConfig() {
return config;
}
public ViewChecker getViewChecker() {
return viewChecker;
}
public void assertEstablishedView() {
assertTrue(getDiscoveryService().getTopology().isCurrent());
}
public VirtualInstanceBuilder getBuilder() {
return builder;
}
public String getDebugName() {
return debugName;
}
}