The Cloud Foundry Integration module provides the adjustments necessary to easily deploy Magnolia on Cloud Foundry.

Cloud Foundry is an open-source, cloud computing platform as a service (PaaS) originally developed by VMware. The platform provides a technical stack that includes the run-time and services on top of a cloud infrastructure. Users can build and deploy any kind of application without the cost and complexity of buying and managing the underlying hardware and software, and provisioning hosting capabilities. All languages and platforms are supported.

In a production-grade PaaS environment several adjustments are typically necessary. The module addresses these and overcomes any platform constraints.

Cloud Foundry is used by different providers such as IBM Bluemix and Pivotal to deliver their PaaS. Each provider has their own specificities and features. A dedicated Magnolia bundle will be provided for each provider. At present, the IBM BlueMix platform and a generic Cloud Foundry platform are supported.

Features of the  Cloud Foundry Integration module include:

  • Out-of the box Magnolia bundle.
  • Auto-injection of bounded services credentials into Magnolia.
  • Java API exposing the Cloud Foundry context (Application settings such as host, Ip, quotas and bounded services ...).
  • Native support for Cloud Foundry providers.
  • Default setup provides an author and clustered public instances.

Installing

To install the Cloud Foundry module include it in your custom web app by adding following maven dependencies to your pom.

  • Generic Cloud Foundry platform
    This Magnolia bundle can be used for your own private cloud or existing Cloud Foundry providers. It uses only standard Cloud Foundry features.

    <!-- replace the magnolia default bundle with these ones in your project parent pom -->
    <dependencyManagement>
    
    	<dependency>
        	<groupId>info.magnolia.cloudfoundry</groupId>
        	<artifactId>cloudfoundry-bundle-ce-webapp</artifactId>
        	<version>${cloudfoundryVersion}</version>
        	<type>war</type>
    	</dependency>
    	<dependency>
       		<groupId>info.magnolia.cloudfoundry</groupId>
    	    <artifactId>cloudfoundry-bundle-ce-webapp</artifactId>
    	    <version>${cloudfoundryVersion}</version>
    	    <type>pom</type>
    	</dependency>
    	<dependency>
    	    <groupId>info.magnolia.cloudfoundry</groupId>
       		<artifactId>cloudfoundry-bundle-ce</artifactId>
       		<version>${cloudfoundryVersion}</version>
        	<type>pom</type>
        	<scope>import</scope>
    	</dependency>
    
    </dependencyManagement>
    
    <!-- replace the magnolia default webapp with these ones in your webapp pom -->
    <dependencies>
    	<dependency>
        	<groupId>info.magnolia.cloudfoundry</groupId>
        	<artifactId>cloudfoundry-bundle-ce-webapp</artifactId>
        	<type>war</type>
    	</dependency>
    	<dependency>
        	<groupId>info.magnolia.cloudfoundry</groupId>
        	<artifactId>cloudfoundry-bundle-ce-webapp</artifactId>
        	<type>pom</type>
    	</dependency>
    </dependencies>
  • IBM BlueMix
    This Magnolia bundle can be used only on IBM BlueMix. It leverages their Liberty buildpack and db2 integration.

    <!-- replace the magnolia default bundle with these ones in your project parent pom -->
    <dependencyManagement>
    
    	<dependency>
        	<groupId>info.magnolia.cloudfoundry</groupId>
        	<artifactId>bluemix-bundle-ce-webapp</artifactId>
        	<version>${bluemixVersion}</version>
        	<type>war</type>
    	</dependency>
    	<dependency>
       		<groupId>info.magnolia.cloudfoundry</groupId>
    	    <artifactId>bluemix-bundle-ce-webapp</artifactId>
    	    <version>${bluemixVersion}</version>
    	    <type>pom</type>
    	</dependency>
    	<dependency>
    	    <groupId>info.magnolia.cloudfoundry</groupId>
       		<artifactId>bluemix-bundle-ce</artifactId>
       		<version>${bluemixVersion}</version>
        	<type>pom</type>
        	<scope>import</scope>
    	</dependency>
    
    </dependencyManagement>
    
    <!-- replace the magnolia default webapp with these ones in your webapp pom -->
    <dependencies>
    	<dependency>
        	<groupId>info.magnolia.cloudfoundry</groupId>
        	<artifactId>bluemix-bundle-ce-webapp</artifactId>
        	<type>war</type>
    	</dependency>
    	<dependency>
        	<groupId>info.magnolia.cloudfoundry</groupId>
        	<artifactId>bluemix-bundle-ce-webapp</artifactId>
        	<type>pom</type>
    	</dependency>
    </dependencies>

Usage

Once you have a valid Magnolia project using one of the Cloud Foundry bundles, the resulting war file can be deployed using the manifest available in the GIT project of the bundle. A deployment script is also provided as a reference.

Limitations

The module has the following known limitations:

  • Janitor is deactivated on the public instances (see Clustering below). Hence the Journal could reach a critical size. One way to solve this issue is to recreate a new fresh cluster regularly.
  • On BlueMix, a single DEA has fairly limited CPU resources. This is not really an issue for the public instances, since they can scale horizontally (adding an instance to the cluster). However, it does limit the author instance where scaling is not feasible.

Known Issues

Security exceptions while loading driver

This warning is raised during startup of the both public and author instance. It might be due to incorrect settings on the JDBC driver of the db2 database.

com.ibm.db2.jcc.am.SqlWarning: Origination unknown: [10228][11541][4.17.29] Security exceptions occurred while loading driver. ERRORCODE=4223, SQLSTATE=null
        at com.ibm.db2.jcc.am.hd.b(hd.java:215)
        at com.ibm.db2.jcc.am.hd.b(hd.java:266)
        at com.ibm.db2.jcc.am.hb.a(hb.java:1327)
        at com.ibm.db2.jcc.am.Connection.initConnection(Connection.java:717)
        at com.ibm.db2.jcc.am.Connection.<init>(Connection.java:694)
        at com.ibm.db2.jcc.t4.b.<init>(b.java:333)
        at com.ibm.db2.jcc.DB2SimpleDataSource.getConnection(DB2SimpleDataSource.java:233)
        at com.ibm.db2.jcc.DB2SimpleDataSource.getConnection(DB2SimpleDataSource.java:199)
        at com.ibm.db2.jcc.DB2Driver.connect(DB2Driver.java:474)
        at com.ibm.db2.jcc.DB2Driver.connect(DB2Driver.java:115)
        at org.apache.commons.dbcp.DriverConnectionFactory.createConnection(DriverConnectionFactory.java:38)
        at org.apache.commons.dbcp.PoolableConnectionFactory.makeObject(PoolableConnectionFactory.java:582)
        at org.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:974)
        at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:106)
        at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
        at org.apache.jackrabbit.core.util.db.ConnectionHelper.getExtraNameCharacters(ConnectionHelper.java:187)
        at org.apache.jackrabbit.core.util.db.ConnectionHelper.prepareDbIdentifier(ConnectionHelper.java:142)
        at org.apache.jackrabbit.core.journal.DatabaseJournal.init(DatabaseJournal.java:262)
        at org.apache.jackrabbit.core.config.RepositoryConfigurationParser$3.getJournal(RepositoryConfigurationParser.java:934)
        at org.apache.jackrabbit.core.config.ClusterConfig.getJournal(ClusterConfig.java:112)
        at org.apache.jackrabbit.core.cluster.ClusterNode.init(ClusterNode.java:230)
        at org.apache.jackrabbit.core.cluster.ClusterNode.init(ClusterNode.java:215)
        at org.apache.jackrabbit.core.RepositoryImpl.createClusterNode(RepositoryImpl.java:667)
        at org.apache.jackrabbit.core.RepositoryImpl.<init>(RepositoryImpl.java:302)
        at org.apache.jackrabbit.core.RepositoryImpl.create(RepositoryImpl.java:615)
        at org.apache.jackrabbit.core.jndi.BindableRepository.createRepository(BindableRepository.java:141)
        at org.apache.jackrabbit.core.jndi.BindableRepository.init(BindableRepository.java:117)
        at org.apache.jackrabbit.core.jndi.BindableRepository.<init>(BindableRepository.java:106)
        at org.apache.jackrabbit.core.jndi.BindableRepositoryFactory.getObjectInstance(BindableRepositoryFactory.java:52)
        at org.apache.jackrabbit.core.jndi.RegistryHelper.registerRepository(RegistryHelper.java:74)
        at info.magnolia.jackrabbit.ProviderImpl.init(ProviderImpl.java:210)
        at info.magnolia.repository.DefaultRepositoryManager.loadRepository(DefaultRepositoryManager.java:187)
        at info.magnolia.repository.DefaultRepositoryManager.loadRepositories(DefaultRepositoryManager.java:173)
        at info.magnolia.repository.DefaultRepositoryManager.init(DefaultRepositoryManager.java:87)
        at info.magnolia.cms.beans.config.ContentRepository.init(ContentRepository.java:141)
        at info.magnolia.cms.beans.config.ConfigLoader.load(ConfigLoader.java:139)
        at info.magnolia.init.MagnoliaServletContextListener$1.doExec(MagnoliaServletContextListener.java:248)
        at info.magnolia.context.MgnlContext$VoidOp.exec(MgnlContext.java:414)
        at info.magnolia.context.MgnlContext$VoidOp.exec(MgnlContext.java:411)
        at info.magnolia.context.MgnlContext.doInSystemContext(MgnlContext.java:385)
        at info.magnolia.init.MagnoliaServletContextListener.startServer(MagnoliaServletContextListener.java:245)
        at info.magnolia.init.MagnoliaServletContextListener.contextInitialized(MagnoliaServletContextListener.java:171)
        at info.magnolia.init.MagnoliaServletContextListener.contextInitialized(MagnoliaServletContextListener.java:125)
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4751)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5175)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:724)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:700)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:714)
        at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1071)
        at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1722)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)

Unable to reset the Stream

This exception is thrown during startup of the public instances (in a cluster). A large blob is added to the Journal (DatabaseJournal:append). Error code = -302 states that the issue could be due to the size of the blob, but the blob appears to be correctly inserted in the table, and the Journal also looks correct (the Journal Idx and the number of rows are equal, so the Journal is complete). In addition, adding new instances does not raise any issue.

[jcc][Thread:Thread-16][SQLException@59d84f30] java.sql.SQLException
[jcc][Thread:Thread-16][SQLException@59d84f30][Sqlca@3f7b22dc] DB2 SQLCA from server
[jcc][Thread:Thread-16][SQLException@59d84f30][Sqlca@3f7b22dc] SqlCode        = -302
[jcc][Thread:Thread-16][SQLException@59d84f30][Sqlca@3f7b22dc] SqlErrd        = { -2146041770, 86, 0, 0, -2744, 0 }
[jcc][Thread:Thread-16][SQLException@59d84f30][Sqlca@3f7b22dc] SqlErrmc       = null
[jcc][Thread:Thread-16][SQLException@59d84f30][Sqlca@3f7b22dc] SqlErrmcTokens = {}
[jcc][Thread:Thread-16][SQLException@59d84f30][Sqlca@3f7b22dc] SqlErrp        = SQLRI24E
[jcc][Thread:Thread-16][SQLException@59d84f30][Sqlca@3f7b22dc] SqlState       = 22001
[jcc][Thread:Thread-16][SQLException@59d84f30][Sqlca@3f7b22dc] SqlWarn        =            
[jcc][Thread:Thread-16][SQLException@59d84f30] SQL state  = 22001
[jcc][Thread:Thread-16][SQLException@59d84f30] Error code = -302
[jcc][Thread:Thread-16][SQLException@59d84f30] Tokens     = null
[jcc][Thread:Thread-16][SQLException@59d84f30] Stack trace follows
com.ibm.db2.jcc.am.SqlDataException: DB2 SQL Error: SQLCODE=-302, SQLSTATE=22001, SQLERRMC=null, DRIVER=4.17.29
        at com.ibm.db2.jcc.am.hd.a(hd.java:739)
        at com.ibm.db2.jcc.am.hd.a(hd.java:66)
        at com.ibm.db2.jcc.am.hd.a(hd.java:135)
        at com.ibm.db2.jcc.am.wo.b(wo.java:2422)
        at com.ibm.db2.jcc.am.wo.c(wo.java:2405)
        at com.ibm.db2.jcc.t4.ab.l(ab.java:408)
        at com.ibm.db2.jcc.t4.ab.a(ab.java:62)
        at com.ibm.db2.jcc.t4.o.a(o.java:50)
        at com.ibm.db2.jcc.t4.ub.b(ub.java:220)
        at com.ibm.db2.jcc.am.xo.sc(xo.java:3526)
        at com.ibm.db2.jcc.am.xo.b(xo.java:4489)
        at com.ibm.db2.jcc.am.xo.mc(xo.java:2833)
        at com.ibm.db2.jcc.am.xo.execute(xo.java:2808)
        at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:172)
        at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:172)
        at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:172)
        at org.apache.jackrabbit.core.util.db.ConnectionHelper.execute(ConnectionHelper.java:518)
        at org.apache.jackrabbit.core.util.db.ConnectionHelper.reallyExec(ConnectionHelper.java:313)
        at org.apache.jackrabbit.core.util.db.ConnectionHelper$1.call(ConnectionHelper.java:294)
        at org.apache.jackrabbit.core.util.db.ConnectionHelper$1.call(ConnectionHelper.java:290)
        at org.apache.jackrabbit.core.util.db.ConnectionHelper$RetryManager.doTry(ConnectionHelper.java:552)
        at org.apache.jackrabbit.core.util.db.ConnectionHelper.exec(ConnectionHelper.java:290)
        at org.apache.jackrabbit.core.journal.DatabaseJournal.append(DatabaseJournal.java:545)
        at org.apache.jackrabbit.core.journal.AppendRecord.update(AppendRecord.java:266)
        at org.apache.jackrabbit.core.cluster.ClusterNode$WorkspaceUpdateChannel.updateCommitted(ClusterNode.java:700)
        at org.apache.jackrabbit.core.state.SharedItemStateManager$Update.end(SharedItemStateManager.java:845)
        at org.apache.jackrabbit.core.state.SharedItemStateManager.update(SharedItemStateManager.java:1537)
        at org.apache.jackrabbit.core.state.LocalItemStateManager.update(LocalItemStateManager.java:400)
        at org.apache.jackrabbit.core.state.XAItemStateManager.update(XAItemStateManager.java:354)
        at org.apache.jackrabbit.core.state.LocalItemStateManager.update(LocalItemStateManager.java:375)
        at org.apache.jackrabbit.core.state.SessionItemStateManager.update(SessionItemStateManager.java:275)
        at org.apache.jackrabbit.core.ItemSaveOperation.perform(ItemSaveOperation.java:258)
        at org.apache.jackrabbit.core.session.SessionState.perform(SessionState.java:216)
        at org.apache.jackrabbit.core.ItemImpl.perform(ItemImpl.java:91)
        at org.apache.jackrabbit.core.ItemImpl.save(ItemImpl.java:329)
        at org.apache.jackrabbit.core.session.SessionSaveOperation.perform(SessionSaveOperation.java:65)
        at org.apache.jackrabbit.core.session.SessionState.perform(SessionState.java:216)
        at org.apache.jackrabbit.core.SessionImpl.perform(SessionImpl.java:361)
        at org.apache.jackrabbit.core.SessionImpl.save(SessionImpl.java:812)
        at info.magnolia.jcr.wrapper.DelegateSessionWrapper.save(DelegateSessionWrapper.java:297)
        at info.magnolia.jcr.wrapper.DelegateSessionWrapper.save(DelegateSessionWrapper.java:297)
        at info.magnolia.jcr.wrapper.DelegateSessionWrapper.save(DelegateSessionWrapper.java:297)
        at info.magnolia.jcr.wrapper.MgnlPropertySettingContentDecorator$MgnlPropertySettingSessionWrapper.save(MgnlPropertySettingContentDecorator.java:464)
        at info.magnolia.jcr.wrapper.DelegateSessionWrapper.save(DelegateSessionWrapper.java:297)
        at info.magnolia.audit.MgnlAuditLoggingContentDecoratorSessionWrapper.save(MgnlAuditLoggingContentDecoratorSessionWrapper.java:82)
        at info.magnolia.ui.admincentral.setup.JcrBrowserContentAppTask.execute(JcrBrowserContentAppTask.java:106)
        at info.magnolia.module.delta.ArrayDelegateTask.execute(ArrayDelegateTask.java:97)
        at info.magnolia.module.ModuleManagerImpl.applyDeltas(ModuleManagerImpl.java:514)
        at info.magnolia.module.ModuleManagerImpl.installOrUpdateModule(ModuleManagerImpl.java:496)
        at info.magnolia.module.ModuleManagerImpl$1.doExec(ModuleManagerImpl.java:274)
        at info.magnolia.context.MgnlContext$VoidOp.exec(MgnlContext.java:414)
        at info.magnolia.context.MgnlContext$VoidOp.exec(MgnlContext.java:411)
        at info.magnolia.context.MgnlContext.doInSystemContext(MgnlContext.java:385)
        at info.magnolia.module.ModuleManagerImpl.performInstallOrUpdate(ModuleManagerImpl.java:268)
        at info.magnolia.module.ui.ModuleManagerWebUI$2.run(ModuleManagerWebUI.java:124)
        at java.lang.Thread.run(Thread.java:745)

One assumption could be a bug in the JDBC layer which has misinterpreted the answer of the db2 database.

Since the Journal is valid, this issue can be ignored.

Cloud Foundry constraints

Cloud Foundry imposes certain constraints on applications in order to maximize its scalability and reliability.

Stateless

An application managed by Cloud Foundry must be stateless. Therefore, it is not possible to permanently store any information locally inside an application container. Each time an application is started, a new Virtual Machine is created and the application reinstalled. This constraint allows you to scale horizontally by adding new instances of the application with a single click.

By default, Magnolia stores the data and the state of the application in the container. The module modifies the default behavior in order to make it compliant with the Cloud Foundry constraints.

Lucene indexes

Lucene indexes are stored on the local filesystem of the application. As a consequence, indexes are rebuilt each time the application container is started. There is no way to store the indexes elsewhere in the infrastructure. Although hacks to store the indexes in a db, infinispan or in a shared memory do exist, they require coding that goes beyond the purpose of this evaluation.

Internal cache

The default implementation for caching is Ehcache. Since Magnolia 5.5.4, the cache is persistent by default.

An alternative to Ehcache is to use a shared cache on top of the public instances (such as Varnish), or cache services offered by the Cloud Foundry provider. The Magnolia cache must be configured to not cache the same assets or pages in order to save memory.

Logs

Logs are stored in the application container and are lost on restart. Cloud Foundry provides a mechanism to send the log to another shared service dedicated to monitoring. The mechanism depends on the chosen provider.

Solr

Magnolia's Solr integration can only be deployed as a Cloud Foundry service since it needs to store its indexes on the local filesystem. A Cloud Foundry service does not have the same constraints as a Cloud Foundry application.

It may not be possible to deploy Magnolia's Solr integration in a Cloud Foundry platform managed by a third-party vendor, such as Pivotal or BlueMix. Typically these platforms do not allow users to create their own private service. The only options are to use a search engine supported by the Cloud Foundry platform (Pivotal supports Searchify), or an external search engine, such as ElasticSearch.

HTTP sessions

Cloud Foundry supports sticky sessions, but does not persist or replicate HTTP session data on the filesystem. If an instance goes down, the user's session is lost. In order to prevent this, sessions must be stored in a shared service such as Redis (provided out-of-the-box by Java buildpack) or Session Cache in BlueMix.

Clustering

Clustering works only if Janitor is deactivated. As a consequence, a new cluster must be recreated regularly in order to restart with a fresh and empty Journal (the old cluster can be destroyed). In Cloud Foundry, this approach can be considered since deploying a new instance is straightforward.

In order to activate Janitor, here are a few approaches which can be investigated:

Recommended Setup

Each supported Cloud Foundry platform provides an out-of the box setup that consists of an author instance and clustered public instances. This section provides details about this setup, and explains how to use it and extend it to your needs.

In the diagrams below, dark-grey boxes represent a Cloud Foundry service. Black circle-ended lines represents a service binding. Magnolia instances are deployed through the Java buildpack in a standard DEA.

Overview

In Cloud Foundry, scalability is either vertical or horizontal:

  • You can increase the allocated memory size for each DEA.
  • You can increase the number of instances in a DEA

To leverage horizontal scalability, the JCR needs to be configured in cluster mode and shared across all the public instances. You can increase the number of public instances with a single click.

The default deployement script and manifest allows you to instanciate such a public cluster.

Suggested enhancements to the setup

As it is, this setup exposes several single points of failure (SPOF) and limitations:

  • The database: If the database crashes, the website is definitely lost.
  • The HTTP sessions: The loss of one of the clusters destroys part of the live sessions on the public instances.
  • The instance cache: Adding a new instance in the cluster or a new cluster means starting with an empty cache.

Here are some approaches to bypass these issues:

  • Using several sets of clusters reduces the risk at the database-level. The Cloud Foundry router must be configured to route dynamically to one of the clusters in the set. Adding a new cluster is straigtforward in Cloud Foundry. However, activation from the author instance to the public instances does take more time. Recreating a new cluster also takes some time since the database must be copied and the Lucene indexes fully recreated. The Synchronization module can also be used in this context.
  • Clustering the database itself. Usually called master/slave, or replication. It is not a SQL standard and thus is specific to each database provider. This approach looks interesting at a first glance since it is totally abstracted from the application and performance is typically better. However, the configuration could be complex and rely heavily on the available tools.
  • Using a shared cache: A shared cache can be used to cache pages and assets, and reduce the number of requests on public instances (for static content). Public instances are configured not to cache this content. Usually, Cloud Foundry platforms provide a global cache out-of the box that can be set up with one click. However, an efficient cache invalidation requires a deeper integration in Magnolia.
  • Using a global HTTP session cache: Cloud Foundry platforms ususally provide this mechanism out-of the box. Sticky sessions are also supported, but do not help in this specific case, because they limit the scalability of the cluster.


#trackbackRdf ($trackbackUtils.getContentIdentifier($page) $page.title $trackbackUtils.getPingUrl($page))