Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration to allow deferring the initial Deployment until after Server is started #10667

Merged
merged 14 commits into from
Oct 17, 2023
Merged
4 changes: 4 additions & 0 deletions jetty-deploy/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,9 @@
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
</dependency>
Comment on lines +62 to +65
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, wrong missing <scope>test</scope>!

Submitted a PR to correct at #10813

</dependencies>
</project>
1 change: 1 addition & 0 deletions jetty-deploy/src/main/config/etc/jetty-deploy.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
</Default>
</Property>
</Set>
<Set name="deferInitialScan"><Property name="jetty.deploy.deferInitialScan" default="false"/></Set>
<Set name="scanInterval"><Property name="jetty.deploy.scanInterval" default="1"/></Set>
<Set name="extractWars"><Property name="jetty.deploy.extractWars" default="true"/></Set>
<Set name="configurationManager">
Expand Down
7 changes: 7 additions & 0 deletions jetty-deploy/src/main/config/modules/deploy.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@ etc/jetty-deploy.xml
# Defaults Descriptor for all deployed webapps
# jetty.deploy.defaultsDescriptorPath=${jetty.base}/etc/webdefault.xml

# Defer Initial Scan
# true to have the initial scan deferred until the Server component is started.
# Note: deploy failures do not fail server startup in a deferred initial scan mode.
# false (default) to have initial scan occur as normal.
# jetty.deploy.deferInitialScan=false

# Monitored directory scan period (seconds)
# jetty.deploy.scanInterval=1

# Whether to extract *.war files
# jetty.deploy.extractWars=true

Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppProvider;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -49,6 +51,7 @@ public abstract class ScanningAppProvider extends ContainerLifeCycle implements
private int _scanInterval = 10;
private Scanner _scanner;
private boolean _useRealPaths;
private boolean _deferInitialScan = false;

private final Scanner.DiscreteListener _scannerListener = new Scanner.DiscreteListener()
{
Expand Down Expand Up @@ -152,9 +155,30 @@ protected void doStart() throws Exception
_scanner.setReportDirs(true);
_scanner.setScanDepth(1); //consider direct dir children of monitored dir
_scanner.addListener(_scannerListener);

_scanner.setReportExistingFilesOnStartup(true);
_scanner.setAutoStartScanning(!_deferInitialScan);
addBean(_scanner);

if (_deferInitialScan)
{
// Setup listener to wait for Server in STARTED state, which
// triggers the first scan of the monitored directories
getDeploymentManager().getServer().addEventListener(
new LifeCycle.Listener()
{
@Override
public void lifeCycleStarted(LifeCycle event)
{
if (event instanceof Server)
{
if (LOG.isDebugEnabled())
LOG.debug("Triggering Deferred Scan of {}", _monitored);
_scanner.startScanning();
}
}
});
}

super.doStart();
}

Expand Down Expand Up @@ -296,6 +320,34 @@ public void setMonitoredDirectories(Collection<String> directories)
}
}

/**
* Test if initial scan should be deferred.
*
* @return true if initial scan is deferred, false to have initial scan occur on startup of ScanningAppProvider.
*/
public boolean isDeferInitialScan()
{
return _deferInitialScan;
}

/**
* Flag to control initial scan behavior.
*
* <ul>
* <li>{@code true} - to have initial scan deferred until the {@link Server} component
* has reached it's STARTED state.<br>
* Note: any failures in a deploy will not fail the Server startup in this mode.</li>
* <li>{@code false} - (default value) to have initial scan occur as normal on
* ScanningAppProvider startup.</li>
* </ul>
*
* @param defer true to defer initial scan, false to have initial scan occur on startup of ScanningAppProvider.
*/
public void setDeferInitialScan(boolean defer)
{
_deferInitialScan = defer;
}

public void setScanInterval(int scanInterval)
{
_scanInterval = scanInterval;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,31 @@
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jetty.deploy.AppProvider;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.test.XmlConfiguredJetty;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand All @@ -52,8 +55,7 @@ public class WebAppProviderTest
private static XmlConfiguredJetty jetty;
private boolean symlinkSupported = false;

@BeforeEach
public void setupEnvironment() throws Exception
private void startEnvironment() throws Exception
{
Path p = testdir.getEmptyPathDir();
jetty = new XmlConfiguredJetty(p);
Expand Down Expand Up @@ -97,8 +99,9 @@ public void teardownEnvironment() throws Exception
}

@Test
public void testStartupContext()
public void testStartupContext() throws Exception
{
startEnvironment();
assumeTrue(symlinkSupported);

// Check Server for Handlers
Expand All @@ -115,8 +118,9 @@ public void testStartupContext()
}

@Test
public void testStartupSymlinkContext()
public void testStartupSymlinkContext() throws Exception
{
startEnvironment();
assumeTrue(symlinkSupported);

// Check for path
Expand All @@ -139,17 +143,11 @@ public void testStartupSymlinkContext()
@EnabledOnOs({LINUX})
public void testWebappSymlinkDir() throws Exception
{
jetty.stop(); //reconfigure jetty

testdir.ensureEmpty();

jetty = new XmlConfiguredJetty(testdir.getEmptyPathDir());
jetty.addConfiguration("jetty.xml");
jetty.addConfiguration("jetty-http.xml");
jetty.addConfiguration("jetty-deploy-wars.xml");

assumeTrue(symlinkSupported);

//delete the existing webapps directory
File webapps = jetty.getJettyDir("webapps");
assertTrue(IO.delete(webapps));
Expand All @@ -159,14 +157,25 @@ public void testWebappSymlinkDir() throws Exception
Files.createDirectory(x.toPath());

//Put a webapp into it
File srcDir = MavenTestingUtils.getTestResourceDir("webapps");
Path srcDir = MavenTestingUtils.getTestResourcePathDir("webapps");
File fooWar = new File(x, "foo.war");
IO.copy(new File(srcDir, "foo-webapp-1.war"), fooWar);
IO.copy(srcDir.resolve("foo-webapp-1.war").toFile(), fooWar);
assertTrue(Files.exists(fooWar.toPath()));

//make a link from x to webapps
Files.createSymbolicLink(jetty.getJettyDir("webapps").toPath(), x.toPath());
assertTrue(Files.exists(jetty.getJettyDir("webapps").toPath()));
try
{
//make a link from x to webapps
Files.createSymbolicLink(jetty.getJettyDir("webapps").toPath(), x.toPath());
assertTrue(Files.exists(jetty.getJettyDir("webapps").toPath()));
symlinkSupported = true;
}
catch (UnsupportedOperationException | FileSystemException e)
{
// if unable to create symlink, no point testing that feature
// this is the path that Microsoft Windows takes.
symlinkSupported = false;
}
assumeTrue(symlinkSupported);

jetty.load();
jetty.start();
Expand All @@ -179,10 +188,6 @@ public void testWebappSymlinkDir() throws Exception
@EnabledOnOs({LINUX})
public void testBaseDirSymlink() throws Exception
{
jetty.stop(); //reconfigure jetty

testdir.ensureEmpty();

Path realBase = testdir.getEmptyPathDir();

//set jetty up on the real base
Expand All @@ -202,8 +207,8 @@ public void testBaseDirSymlink() throws Exception
jetty.stop();

//Make a symbolic link to the real base
File testsDir = MavenTestingUtils.getTargetTestingDir();
Path symlinkBase = Files.createSymbolicLink(testsDir.toPath().resolve("basedirsymlink-" + System.currentTimeMillis()), jettyHome);
Path testsDir = MavenTestingUtils.getTargetTestingPath();
Files.createSymbolicLink(testsDir.resolve("basedirsymlink-" + System.currentTimeMillis()), jettyHome);
Map<String, String> properties = new HashMap<>();
properties.put("jetty.home", jettyHome.toString());
//Start jetty, but this time running from the symlinked base
Expand All @@ -215,7 +220,6 @@ public void testBaseDirSymlink() throws Exception
try
{
server.start();
HandlerCollection handlers = (HandlerCollection)server.getHandler();
Handler[] children = server.getChildHandlersByClass(WebAppContext.class);
assertEquals(1, children.length);
assertEquals("/foo", ((WebAppContext)children[0]).getContextPath());
Expand All @@ -225,12 +229,62 @@ public void testBaseDirSymlink() throws Exception
server.stop();
}
}

private Map<String, String> setupJettyProperties(Path jettyHome)

@Test
public void testDelayedDeploy() throws Exception
{
Path realBase = testdir.getEmptyPathDir();

//set jetty up on the real base
jetty = new XmlConfiguredJetty(realBase);
jetty.addConfiguration("jetty.xml");
jetty.addConfiguration("jetty-http.xml");
jetty.addConfiguration("jetty-deploy-wars.xml");

//Put a webapp into the base
jetty.copyWebapp("foo-webapp-1.war", "foo.war");

Path jettyHome = jetty.getJettyHome().toPath();

Map<String, String> properties = new HashMap<>();
properties.put("jetty.home", jettyHome.toFile().getAbsolutePath());
return properties;
properties.put("jetty.home", jettyHome.toString());
properties.put("jetty.deploy.deferInitialScan", "true");
//Start jetty, but this time running from the symlinked base
System.setProperty("jetty.home", properties.get("jetty.home"));

List<Resource> configurations = jetty.getConfigurations();
Server server = XmlConfiguredJetty.loadConfigurations(configurations, properties);

try
{
server.start();

DeploymentManager deploymentManager = server.getBean(DeploymentManager.class);
for (AppProvider appProvider : deploymentManager.getAppProviders())
{
if (appProvider instanceof ScanningAppProvider)
{
ScanningAppProvider scanningAppProvider = (ScanningAppProvider)appProvider;
assertTrue(scanningAppProvider.isDeferInitialScan(), "The DeferInitialScan configuration should be true");
}
}

// Wait till the webapp is deployed and started
await().atMost(Duration.ofSeconds(5)).until(() ->
{
Handler[] children = server.getChildHandlersByClass(WebAppContext.class);
if (children == null || children.length == 0)
return false;
WebAppContext webAppContext = (WebAppContext)children[0];
if (webAppContext.isStarted())
return webAppContext.getContextPath();
return null;
}, is("/foo"));
}
finally
{
server.stop();
}
}

private static boolean hasJettyGeneratedPath(File basedir, String expectedWarFilename)
Expand Down
Loading