JSF in the cloud : Finding the PaaS - Part 2 : OpenShift
Introduction
OpenShift is Red Hat cloud offering.
Like Google App Engine (GAE), it supports many languages. It is newer than GAE and can not claim that it is used to run a performance monster like the Google Search Engine.
Its specification sheet looked very interesting to me for many reasons :
- It is fully free software ;
- It is standard based ;
- It proposes Tomcat 6 or 7 as webapp container ;
- Private cloud offering is not a third party project. It is in the main offering.
Moreover :
- Its free offer includes 3 apps, 3 "small gears" (~3 VMS), access to a database instance. With a free account, you can have a jenkins instance, for continuous integration, a "gear" for your webapp and use a PostgreSQL database.
- It gives you extensive control on the configuration of the webapp container. You can even use a custom realm by putting the realm's jar and dependencies (ex: a DB driver) in a custom dir and then adding it to the path in a catalina.properties file (see this blog entry for a detailed explanation).
In fact, I could not find a thing that I usually do that I could not do with OpenShift. I still have to check if I can do parallel deployment with it, but it might not be relevant in a cloud based app, where one might instanciate VMs running the new version rather than upgrading running instances.
After some (simple but blocking) configuration trouble, OpenShift delivers. It is both fast and convenient. It clearly is a very interesting alternative for running a PrimeFaces / MyFaces / Deltaspike / OpenWebBeans stack.
Tomcat 7 cartridge
Once you have created an account and installed rhc client tools, instantiating a VM (a "gear") having Tomcat 7 and PostgreSQL 9.2 is as simple as :
lpenet@boulier:~/openshift$ rhc app create testlp jbossews-2.0 postgresql-9.2 Application Options ------------------- Domain: penet Cartridges: jbossews-2.0, postgresql-9.2 Gear Size: default Scaling: no Creating application 'testlp' ... done PostgreSQL 9.2 database added. Please make note of these credentials: Root User: not-your-business Root Password: not-your-business-too Database Name: testlp Connection URL: postgresql://$OPENSHIFT_POSTGRESQL_DB_HOST:$OPENSHIFT_POSTGRESQL_DB_PORT Waiting for your DNS name to be available ... done Clonage dans 'testlp'... The authenticity of host 'testlp-penet.rhcloud.com (54.166.197.252)' can't be established. RSA key fingerprint is cf:ee:77:cb:0e:fc:02:d7:72:7e:ae:80:c0:90:88:a7. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added 'testlp-penet.rhcloud.com,54.166.197.252' (RSA) to the list of known hosts. Your application 'testlp' is now available. URL: http://testlp-penet.rhcloud.com/ SSH to: 547208c94382eccd73000002@testlp-penet.rhcloud.com Git remote: ssh://547208c94382eccd73000002@testlp-penet.rhcloud.com/~/git/testlp.git/ Cloned to: /home/lpenet/openshift/testlp Run 'rhc show-app testlp' for more details about your app.
You can use your usual tools, such as pgadmin3 to admin the database. To do so, just create port forwardings, in crypted tunnels using
lpenet@boulier:~/openshift$ rhc port-forward testlp Checking available ports ... done Forwarding ports ... Address already in use - bind(2) while forwarding port 5432. Trying local port 5433 To connect to a service running on OpenShift, use the Local address Service Local OpenShift ---------- -------------- ---- ---------------- java 127.0.0.1:8080 => 127.4.111.1:8080 postgresql 127.0.0.1:5433 => 127.4.111.2:5432 Press CTRL-C to terminate port forwarding
then connect pgadmin3 to the server listening on host 127.0.0.1 port 5433 (in this case).
As you could see, the rhc app create testlp jbossews-2.0 postgresql-9.2
automatically pulled the app source code.
The default source tree is quite small, as for GAE :
lpenet@boulier:~/openshift/testlp$ tree . ├── pom.xml ├── README.md ├── src │ └── main │ ├── java │ ├── resources │ └── webapp │ ├── images │ │ └── jbosscorp_logo.png │ ├── index.html │ ├── snoop.jsp │ └── WEB-INF │ └── web.xml └── webapps
There are no IDE specific files there, as for GAE. Application deployment is mostly handled by git.
The default pom.xml is quite simple :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>testlp</groupId> <artifactId>testlp</artifactId> <packaging>war</packaging> <version>1.0</version> <name>testlp</name> <repositories> <repository> <id>eap</id> <url>http://maven.repository.redhat.com/techpreview/all</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>eap</id> <url>http://maven.repository.redhat.com/techpreview/all</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> </pluginRepositories> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.6</maven.compiler.source> <maven.compiler.target>1.6</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.2-1003-jdbc4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.25</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> </dependencies> <profiles> <profile> <!-- When built in OpenShift the 'openshift' profile will be used when invoking mvn. --> <!-- Use this profile for any OpenShift specific customization your app will need. --> <!-- By default that is to put the resulting archive into the 'webapps' folder. --> <!-- http://maven.apache.org/guides/mini/guide-building-for-different-environments.html --> <id>openshift</id> <build> <finalName>testlp</finalName> <plugins> <plugin> <artifactId>maven-war-plugin</artifactId> <version>2.1.1</version> <configuration> <outputDirectory>webapps</outputDirectory> <warName>ROOT</warName> </configuration> </plugin> </plugins> </build> </profile> </profiles> </project>
It only adds Red Hat repositories, usual JDBC drivers and servlet 3.0 API spec.
Compared to GAE, we can see that it is Java 6 (7 for GAE) and Servlet API 3.0 (2.5 for GAE).
So, Java 6 seems to be the default choice. We can also request java 7 by adding a marker in the .openshift/markers directory. In fact, as this marker is present by default in the Tomcat7 cartridge, java7 is used by default with Tomcat 7 and we can safely upgrade to java version 1.7 in the pom.xml.
Having Servlet API 3.0 is more important, as it is the official target of MyFaces 2.2. Having Servlet API 2.5 instead of 3.0 introduces very few JSF related restrictions (I can think of nothing excepted file upload), but this might change and can avoid annoying "bugs", as with MyFaces 2.2.5 which used an API available only in servlet API 3.0 (see MYFACES-3923).
I tried to configure the test project in a way similar to what I did for GAE, just removing the JNDI hacks.
I had a hard time getting it up and running, for a simple (once you found it) reason : the jbossews-2.0 cartridge default config does not unpack application WAR archives. And this blocks OpenWebBeans and other libraries like Weld that I tested from running. They did not find my beans, did not instantiate them, so it did not work.
The fix is simple : edit the .openshift/config/server.xml
file so that unpackWARs is set to true :
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
I wonder why this is the default config of this cartridge. This is not a usual way to proceed with tomcat. Anyway, it works fine. You can find the project under git.
Getting my a bit-more-complexe example, which also uses JPA, Hibernate and JNDI was very simple too, once you unpack the war. I just added the following openshift profile :
<profile> <!-- When built in OpenShift the 'openshift' profile will be used when invoking mvn. --> <!-- Use this profile for any OpenShift specific customization your app will need. --> <!-- By default that is to put the resulting archive into the 'webapps' folder. --> <!-- http://maven.apache.org/guides/mini/guide-building-for-different-environments.html --> <id>openshift</id> <properties> <dosleg.url>jdbc:postgresql://${env.OPENSHIFT_POSTGRESQL_DB_HOST}/doslegtest</dosleg.url> <dosleg.user>dosleg</dosleg.user> <dosleg.password>dosleg</dosleg.password> </properties> <build> <finalName>doslegtest</finalName> <plugins> <plugin> <artifactId>maven-war-plugin</artifactId> <version>2.1.1</version> <configuration> <outputDirectory>webapps</outputDirectory> <warName>ROOT</warName> </configuration> </plugin> </plugins> </build> </profile>
It is a copy/paste of the default openshift profile, plus adjusting dosleg.* properties, leveraging openshift vars. DB user and password are hard coded there. Using environment variables would be straightforward.
Debugging
The documentation suggests to forward ports, using the rhc port-forward
, then to attach your debugger on the forwarded 8787 port.
This is both a bit frustrating when you have to debug start-up related problems and just hell slow for all other purposes.
Red Hat suggests to install the free software openshift origin on premises for development. Doing so solves the slowness problem, but still does not a development environment as polished as GAE or Heroku. With those two lasts, you can easily easily and automatically download and run the exact same software versions used on the server.
I, however, have been using untared versions of Tomcat to develop webapps deployed on production systems using Red Hat packages versions for years without many real problems. So, it might not be such a big deal.
There is no openshift netbeans plugin. However, some people at Oracle seems to think that it would be very easy to develop one.
Other cartridges : Tomcat 7 community cartridge, WildFly and TomEE
Another option with OpenShift is to use another Java Web cartridge. OpenShift provides a JBoss 7.1 cartridge. JBoss can be an interesting alternative to me. It is Tomcat based, so I can recycle my knowledge of its security mechanisms, such as realms. However, JBoss 7.1 is not JSF 2.2. So, it is not an up to date alternative.
We can, of course, use a community cartridge. There is a community tomcat 7 cartridge (and of course a community tomcat 8 cartridge). Romain Manni Bucau documented how to build a TomEE cartridge (which is basically the same stack I use) . But all those cartridges do not get automatic security updates from Open Shift, which is a major drawback.
I played a bit with OpenShift WildFly cartridge. It is a tempting alternative, as WildFly 8 is a JEE7. It is not the same stack (Mojarra instead of MyFaces and Weld instead of OpenWebBeans), but this can be acceptable in some circumstances, as it also neatly integrates DeltaSpike and uses Tomcat 7 as container, so that I can recycle what I know on security. The experience proved interesting. The pom.xml is quite simple :
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>wildfly8</groupId> <artifactId>wildfly8</artifactId> <packaging>war</packaging> <version>1.0</version> <name>wildfly8</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <maven.compiler.fork>true</maven.compiler.fork> <version.jboss.spec.javaee.7.0>1.0.0.Final</version.jboss.spec.javaee.7.0> <version.wildfly.maven.plugin>1.0.2.Final</version.wildfly.maven.plugin> </properties> <dependencyManagement> <dependencies> <!-- Define the version of JBoss' Java EE 7 APIs we want to import. Any dependencies from org.jboss.spec will have their version defined by this BOM --> <!-- JBoss distributes a complete set of Java EE 7 APIs including a Bill of Materials (BOM). A BOM specifies the versions of a "stack" (or a collection) of artifacts. We use this here so that we always get the correct versions of artifacts. Here we use the jboss-javaee-7.0 stack (you can read this as the JBoss stack of the Java EE 7 APIs). You can actually use this stack with any version of WildFly that implements Java EE 7, not just WildFly 8! --> <dependency> <groupId>org.jboss.spec</groupId> <artifactId>jboss-javaee-7.0</artifactId> <version>${version.jboss.spec.javaee.7.0}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>javax.enterprise</groupId> <artifactId>cdi-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.spec.javax.annotation</groupId> <artifactId>jboss-annotations-api_1.2_spec</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.spec.javax.faces</groupId> <artifactId>jboss-jsf-api_2.2_spec</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.spec.javax.ejb</groupId> <artifactId>jboss-ejb-api_3.2_spec</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.14.4</version> <scope>provided</scope> </dependency> <!-- primefaces --> <dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> <version>5.1</version> <type>jar</type> </dependency> </dependencies> <profiles> <profile> <!-- When built in OpenShift the 'openshift' profile will be used when invoking mvn. --> <!-- Use this profile for any OpenShift specific customization your app will need. --> <!-- By default that is to put the resulting archive into the 'deployments' folder. --> <!-- http://maven.apache.org/guides/mini/guide-building-for-different-environments.html --> <id>openshift</id> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> <outputDirectory>deployments</outputDirectory> <warName>ROOT</warName> </configuration> </plugin> </plugins> </build> </profile> <profile> <activation> <activeByDefault>true</activeByDefault> </activation> <build> <plugins> <!-- WildFly plugin to deploy war --> <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-maven-plugin</artifactId> <version>${version.wildfly.maven.plugin}</version> <configuration> <skip>false</skip> </configuration> </plugin> </plugins> </build> </profile> </profiles> </project>
The web.xml too :
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" metadata-complete="false"> <display-name>Hello JSF</display-name> <welcome-file-list> <welcome-file>accueil.xhtml</welcome-file> </welcome-file-list> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.xhtml</url-pattern> </servlet-mapping> </web-app>
Locally debugging seems as simple as downloading and unpacking Wildfly 8 archive then fire your favorite debugger. There is a Netbeans plugin that makes this very easy. However, it seemed as easy to do the same for Tomcat 7 and proved wrong...
WildFly configuration seems very similar to Tomcat configuration, the main difference being that there is no more context.xml file. An example of JNDI configuration port is available here and here. There even seems to be a book on the topic of Tomcat to WildFly migration.
Conclusion
OpenShift is a good option for my java stack. I could get my PrimeFaces / MyFaces / Deltaspike / OpenWebBeans stack to run on their JBoss EWS 2.0 Tomcat 7 cartridge. Other cartridges seem interesting, but they lack automatic security upgrades, which I expect from a PaaS offering.
The only problem seems to be the debugging experience. After a few years of developement with Tomcat, I tend to think that it can be quite acceptable. Real developement could be performed with a local Tomcat 7 server. Final tests could be performed on a developement OpenShift system, using OpenShift Origin.