Skip to content

Add "start-stop" goals for the spring-boot-maven-plugin #2525

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

Closed
danpersa opened this issue Feb 19, 2015 · 32 comments
Closed

Add "start-stop" goals for the spring-boot-maven-plugin #2525

danpersa opened this issue Feb 19, 2015 · 32 comments
Assignees
Labels
type: enhancement A general enhancement
Milestone

Comments

@danpersa
Copy link

In order to run integration tests for a spring boot app, you can use the workaround described in #667.

There is a more detailed article here.

But in some cases, this approach can't be used. One example would be running Cucumber tests.

I have two use cases: The first one uses maven. I want to start my spring-boot app, run some Cucumber tests, then stop the spring-boot app, everything within Maven.

Another use case would be: Having the spring-boot running in my IDE and then run the Cucumber tests also from the IDE, by connecting to the already running app.

I wouldn't like to have the deployment phase hardcoded into the test class as in the example article. This is because (as described in the second use case) if I already have an instance of the application running, I should be able to run my cucumber test directly from my IDE, without starting another instance. I'd like to decouple the deployment step and testing step, as they don't really belong together.

Another reason for having the start-stop approach in the maven plugin is that the method of running the tests described in the article doesn't help me, because Cucumber has to be run using a @RunWith(@Cucumber.class) junit annotation, and the spring boot need the @RunWith(SpringJUnit4ClassRunner.class) annotation. And we can't use two @RunWith annotations on one test class.

I managed to start my spring boot app using maven, like this:

    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <mainClass>boot.Main</mainClass>
            <layout>ZIP</layout>
        </configuration>
        <executions>
            <execution>
                <id>boot-run</id>
                <goals>
                    <goal>run</goal>
                </goals>
                <phase>pre-integration-test</phase>
                <configuration>
                    <fork>true</fork>
                </configuration>
            </execution>
        </executions>
    </plugin>

But after the boot-app is started, the maven build is blocked... As soon as you press Ctrl+C, to stop the tomcat, the build resumes.

@philwebb philwebb added the type: enhancement A general enhancement label Feb 21, 2015
@philwebb philwebb added this to the 1.3.0 milestone Feb 21, 2015
@chrylis
Copy link
Contributor

chrylis commented Feb 26, 2015

This would definitely be helpful.

@snicoll snicoll self-assigned this Feb 26, 2015
@ulich
Copy link

ulich commented Mar 5, 2015

i am having exactly the same problem. +1

@snicoll
Copy link
Member

snicoll commented Mar 11, 2015

From what I can see, forking will be hard (thought not impossible) to support. I've been looking for various ways to do this and the JMX route seems the cleanest. The process that starts the application pass an extra internal property that will register an MBean on the platform mbean server (the mbean server of the current VM). Stop will use that reference to request the context to gracefully shutdown.

This works fine but now we have another problem: since we are invoking a main method, we have no good way to know that the application has started. Surely SpringApplicationRunListener sounds like a good choice but there is no way to register that externally.

@snicoll
Copy link
Member

snicoll commented Mar 11, 2015

Looks like that using JMX to figure out if the app is running is a good choice as well. Trying that.

snicoll added a commit to snicoll/spring-boot that referenced this issue Mar 13, 2015
SpringApplicationLifecycle provides lifecycle operations on the current
Spring Boot application. It can be registered to the mBean server of the
current platform if a specific property is set.

The Maven plugin uses that lifecycle to trigger a proper shutdown of the
application.

See spring-projectsgh-2525
snicoll added a commit to snicoll/spring-boot that referenced this issue Mar 13, 2015
This is an attempt at finalizing the feature defined in spring-projectsgh-2525.

Including fork support brought a lot of complexity for no good reason
and right now the Maven process is unable to connect to the MBean server
of a different process for no good reason. jconsole can connect just fine
and the same code works "in-process".
snicoll added a commit to snicoll/spring-boot that referenced this issue Mar 13, 2015
SpringApplicationLifecycle provides lifecycle operations on the current
Spring Boot application. It can be registered to the platform mBean
server of the if a specific property is set. Besides, the JMX name can
also be customized via a property in case more than one Spring Boot
application is started in the same process.

The Maven plugin uses that MBean to check that the application is ready
before ending the "start" phase. It uses it to trigger a proper shutdown
of the application during the "stop" phase.

Such change permits the maven plugin to integrate a classical integration
test scenario where the "start" goal is invoked during the
pre-integration phase and the "stop" goal during the post-integration
phase.

See spring-projectsgh-2525
@snicoll
Copy link
Member

snicoll commented Mar 13, 2015

okay so those who are interested to try this out are welcome to use a local build of my gh-2525 branch branch. This should work out-of-the-box as long as forking is not enabled (more on that later). For instance, the following configuration should run your integration tests

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <id>pre-integration-test</id>
                    <goals>
                        <goal>start</goal>
                    </goals>
                </execution>
                <execution>
                    <id>post-integration-test</id>
                    <goals>
                        <goal>stop</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.18.1</version>
            <executions>
                <execution>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

We are using a property to trigger an auto-configuration that registers a MBean who serves two purposes:

  1. Tell us if the application is ready (so that your integration tests really start when they have to). This is using the new ApplicationReadyEvent (see Add ApplicationReadyEvent #2638)
  2. Stop the application gracefully (basically closing the ApplicationContext).

So what's the problem with fork? The short answer is that I don't know. Since we are using JMX, we can also start the process with the necessary system properties to expose the MbeanServer on a given port and connect to that port to invoke the exact same operations. But for some reasons it does not work while the same code outside of the maven plugin works. I am sure I am missing something.

If you want to try it out (code unpolished), check the gh-2525-remote.

@derickfernando
Copy link

Nice, I just got this working. I did have to make SpringApplicationLifecycleAutoConfiguration public because I am not using @EnableAutoConfiguration. I hope you can consider that change. I'll provide more feedback once I implement more integration tests.

@snicoll
Copy link
Member

snicoll commented Mar 20, 2015

Yes, I intend to reuse that for something else actually (windows service that can start/stop a boot app). Can you share a bit more about your use case? (why you're not using auto-config I mean). Thanks for testing!

@derickfernando
Copy link

I'm on an earlier version of spring boot (1.1.8) and I recall needing spring-mvc, which in turn brought in the freemarker dependency. I don't actually have any freemarker templates, so the application would complain that it could not find the template location. I would have to add spring.freemarker.checkTemplateLocation=false to the properties. Instead I removed @EnableAutoConfiguration and used just the AutoConfiguration classes I needed.

@snicoll
Copy link
Member

snicoll commented Mar 24, 2015

I would strongly advise you to restore to a proper use of the auto-configuration. All those hacks for a property change seems a bit extreme to me. Anyway, we won't backport that code in 1.1.x

@derickfernando
Copy link

I'll be upgrading to the latest soon. Thanks.

snicoll added a commit to snicoll/spring-boot that referenced this issue Mar 27, 2015
SpringApplicationLifecycle provides lifecycle operations on the current
Spring Boot application. It can be registered to the platform mBean
server of the if a specific property is set. Besides, the JMX name can
also be customized via a property in case more than one Spring Boot
application is started in the same process.

The Maven plugin uses that MBean to check that the application is ready
before ending the "start" phase. It uses it to trigger a proper shutdown
of the application during the "stop" phase.

Such change permits the maven plugin to integrate a classical integration
test scenario where the "start" goal is invoked during the
pre-integration phase and the "stop" goal during the post-integration
phase.

See spring-projectsgh-2525
snicoll added a commit to snicoll/spring-boot that referenced this issue Mar 27, 2015
Update the maven plugin to support forked process as well. If a forked
process is required, the platform MBean Server is exposed using a
configurable JMX port that is latter used to access the application.

See spring-projectsgh-2525
@snicoll
Copy link
Member

snicoll commented Mar 27, 2015

Support for fork process has been added.

@philwebb
Copy link
Member

@snicoll I've needed to revert this as there are a few kinks that I think we need to work though first. Can we move this is branch and iterate on it a little bit?

Specifically these are my initial concerns:

spring-boot-maven-tests

We've got into trouble before trying to use the spring-boot-maven plugin from a project that's part of the reactor. We probably need to move these to samples or perhaps create an integration test in the plugin that doesn't depend on Boot (perhaps mock the JMX stuff)

SpringApplicationLifecycleAutoConfiguration

The lifecycle JMX stuff feels quite fundamental and I wonder if it would be better in spring-boot rather than spring-boot-autoconfigure.

Property passing in StartMojo

Using -- arguments seems a little invasive and it relies on the fact that addCommandLineProperties is true. I wonder is system properties might be a more reliable way to pass properties.

@philwebb philwebb reopened this Apr 16, 2015
snicoll added a commit to snicoll/spring-boot that referenced this issue Apr 17, 2015
SpringApplicationLifecycle provides lifecycle operations on the current
Spring Boot application. It can be registered as an MBean of the platform
MBean server if a specific property is set. Besides, the JMX name can
also be customized via a property in case more than one Spring Boot
application is started in the same process.

The Maven plugin uses that MBean to check that the application is ready
before ending the "start" phase. It uses it to trigger a proper shutdown
of the application during the "stop" phase.

If the process has to be forked, the platform MBean server is exposed on
a configurable port so that the maven plugin can connect to it.

Such change permits the maven plugin to integrate a classical integration
test scenario where the "start" goal is invoked during the
pre-integration phase and the "stop" goal during the post-integration
phase.

Closes spring-projectsgh-2525
@snicoll
Copy link
Member

snicoll commented Apr 17, 2015

OK. I've created a new gh-2525 branch on my fork. We can work from there.

Regarding your concerns:

  1. I vaguely remember something of the kind, yes but I don't think this is the case anymore. I tried to run mvn release:prepare -DdryRun=true but it complained that the SCM connection is not set.
  2. Right, I have moved that to spring-boot in snicoll@2c7f486 - Actually moving the JMX client there would be very useful as well if another tool would like to use it (service wrapper) but we have an obvious dependency issue.
  3. The only reason I did it that way was because passing system properties to a non-forked process is weird. But relying on that property to be true is not that good either.

Let me know how you want to iterate on this.

@php-coder
Copy link

@snicoll Could I ask you to create a tag on gh-2525 branch? In this case I be able to build JAR via https://jitpack.io and test it.

@snicoll
Copy link
Member

snicoll commented May 19, 2015

@php-coder you can fork my fork and create whatever tag you want :) That being said, I'd checkout and run a full build actually: this gives you 1.3.0.BUILD-SNAPSHOT that you can use against https://start.spring.io for instance.

I've been discussing this with @philwebb yesterday and I'll resume work on it this week still. We really want this to be part of 1.3.0.M1 that should be released soon.

If you try this out, please report here, thanks!

@dsyer dsyer removed the in progress label May 19, 2015
@snicoll snicoll modified the milestones: 1.3.0.M1, 1.3.0.RC1 May 19, 2015
wilkinsona added a commit that referenced this issue May 19, 2015
 - Verify that isReady has been called
 - When forking, use a random port for JMX
 - Don’t wait for application termination as it introduces a race
   condition and verifying that shutdown has been requested is
   sufficient

See gh-2525
@php-coder
Copy link

@snicoll After switching to 1.3.0.BUILD-SNAPSHOT application stop running at all with exception NoClassDefFoundError: org/springframework/context/event/GenericApplicationListener. Looks like I need to update dependencies to Spring Framework 4.2 or waiting when #2947 will be solved.

php-coder added a commit to php-coder/mystamps that referenced this issue May 19, 2015
@snicoll
Copy link
Member

snicoll commented May 19, 2015

Master rely on 4.2 snapshot so you have to use that, yes.

@php-coder
Copy link

Forget my last comment, please. I forgot to remove spring-framework-bom and I used 4.1.6 version.

@php-coder
Copy link

Finally, I've got it working! Thanks!

@snicoll
Copy link
Member

snicoll commented May 26, 2015

👍

@mbert
Copy link

mbert commented Jun 10, 2015

Could it be that the new functionality is broken at the moment? I had it running with these settings:

  • spring 4.2.0.RC1
  • spring-boot 1.3.0.BUILD-SNAPSHOT
  • spring-boot-maven-plugin 1.3.0.BUILD-SNAPSHOT

Here's the relevant section from my pom.xml:

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${springboot.version}</version>
                <executions>
                    <execution>
                        <id>pre-integration-test</id>
                        <goals>
                            <goal>start</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>post-integration-test</id>
                        <goals>
                            <goal>stop</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <configuration>
                    <groups>integration</groups>
                    <includes>
                        <include>**/*.java</include>
                    </includes>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

And the relevant annotations from my main class:

    @EnableAutoConfiguration
    @ComponentScan
    @ImportResource({ "classpath*:applicationContext.xml", "classpath*:handlers/*.xml" })

Now when I run 'mvn install', my tomcat application comes up and is functional (I can run the integration tests against it from the IDE while it's up), but the plugin fails to notice that it is up, so after a timeout I end up with this error:

    Spring application did not start before the configured timeout (30000ms -> [Help 1]

With this very same setup my integration tests were still running one or two weeks ago. Am I doing something wrong? Or is there a problem with the current snapshots?

@snicoll
Copy link
Member

snicoll commented Jun 10, 2015

It could have been broken this morning. Could you please try again with the current SNAPSHOT?

@mbert
Copy link

mbert commented Jun 10, 2015

Just tried again with a clean ~/.m2/repository, same problem.
The exact version of the plugin is BUILD-20150610.141951-223
I am using the springsource snaphots repository.
Anything else I could try?

@snicoll
Copy link
Member

snicoll commented Jun 10, 2015

Nope, it's still broken. Thanks for the report! Follow #3124 for further updates

@mbert
Copy link

mbert commented Jun 10, 2015

(Y)

@snicoll
Copy link
Member

snicoll commented Jun 10, 2015

The issue is fixed but you'll have to wait an hour to get a fresh snapshot with the fix. You can also build locally if you want. Thanks!

@mbert
Copy link

mbert commented Jun 10, 2015

👍

php-coder added a commit to php-coder/mystamps that referenced this issue Jun 21, 2015
php-coder added a commit to php-coder/mystamps that referenced this issue Jun 22, 2015
@gauravbrills
Copy link
Contributor

@php-coder hwo did you resolve the issue with GenericApplicationListener I am facing the same when upgrading to >1.3.0.M1 current pom is like

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.0.M1</version>
    </parent>
<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>com.wellmanage.whf.service.HfServices</start-class>
        <spring.version>4.2.0.RC1</spring.version>
        <spring-data-releasetrain.version>Gosling-BUILD-SNAPSHOT</spring-data-releasetrain.version>
        <java.version>1.8</java.version><!-- <spring-hateoas.version>0.18.0.BUILD-SNAPSHOT</spring-hateoas.version> -->
        <json-path.version>1.2.0</json-path.version>
    </properties>

@snicoll
Copy link
Member

snicoll commented Jun 28, 2015

@gauravbrills you are forcing a Spring framework version yourself. Please note that 1.3.0.BUILD-SNAPSHOT requires Spring Framework 4.2.0.BUILD-SNAPSHOT

It would be much better if you were not forcing these versions yourself. Can't you use the spring-boot-dependencies bom instead? (your own build file would be much cleaner!)

See the documentation

@gauravbrills
Copy link
Contributor

Thanks @snicoll ya done that also but still some 4.1.6 dependecy are coming in not sure why I think those are causing the issue .Let me check more

@snicoll
Copy link
Member

snicoll commented Jun 28, 2015

If you can't trace it down please open a stackoverflow thread. Thanks.

@gauravbrills
Copy link
Contributor

@snicoll it worked thanks .. using the BOM was a silly error from my side cheers .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

10 participants