Monday, March 4, 2013

Multiple P2 Profile Support in Carbon

In this blog-post I'm going to discuss about the proposed multiple P2 Profile support in Carbon. To get a better understanding of this effort, we first need to understand the basics about P2.
P2 is the provisioning framework used by WSO2 Carbon platform to install features. To get an overall understanding of P2 concepts please refer the Eclipse wiki 

The main areas to discuss here are, P2 Profile, how we generate P2 data using P2-Director and how we manage the Eclipse runtime by passing relevant configurations to start an Eclipse based product.

P2 Profile represents the state of all the installation units (IUs) that constitute an Eclipse based product. P2 Profile keeps track of all installed, uninstalled and updated IUs in the product.
In Carbon platform the IUs are features, and a Carbon product is constituted with Carbon Kernel + a set of features. 

P2 Director is the a command line tool for installing additional software or uninstalling IUs from an Eclipse-based product. This is the tool we use underneath the carbon-p2-plugin to build & provision features our products. 

Until now, Carbon products have been using a single P2 Profile:  WSO2CarbonProfile. In all our products we have been using WSO2CarbonProfile to install different features and constitute different Carbon products.
Now we will look at how we can support multiple P2 profiles in a single product distribution. This will make it possible to have multiple Profiles pre-installed in a single product and select the profile to start which will load a separate Product at runtime. 

Let me explain how we generate our product distributions using carbon-p2-plugin to have a multi-profile installation.

1. How we generate product distributions  

To see how we can generate the new structure to support multiple-profiles we should first look at how the product distributions are created currently via carbon-p2-plugin using default WSO2CarbonProfile

1. In Kernel distribution 

We first constitute the carbon product using the carbon product configuration file: carbon.product 

carbon.product file has the initial configurations of the osgi-bundles to start and other required configurations necessary to generate the config.ini (the main configuration file for an eclipse product)

It also defines the minimal equinox runtime-feature to be installed first into the default WSO2Carbon profile.



So in the first 2 phases in carbon-p2-plugin (publish-product, materialize-product) we invoke the p2-director to use above carbon.product and materialize the default WSO2CarbonProfile with a minimal runtime.

Then in the profile-gen phase we install carbon core feature to the WSO2CarbonProfile.



2. In product distributions

We extract the carbon-core distribution and install additional features to the default WSO2CarbonProfile in profile-gen phase.

So in the final output the target, the product distribution structure with a single Profile looks like this:



When supporting multiple-profiles, we have to first do the process of materializing the a Profile, before installing features to it.

This can be achieved using the same carbon.product configuration file as all our products use the same minimal runtime to install features to the Profile afterwards. 

Which means for each new P2 Profile we will have to to do the initial materializing part using carbon.product configuration file and install features on top of it.
For this, we'll have to repeat the materialize-product and p2-profile-gen goals for each Profile we generate. 
Please refer this pom in the POC done. Here in the p2-profile-gen module of the carbon product I'm creating 2 profiles. 
WSO2CarbonProfile and WSO2SampleProfile.

So the new product distribution structure now looks like below; 
Each profile installed will have a separate configuration directory, and all the Profiles will share the same P2 data-area to store P2 data.




Now let's look at how we can generate above structure using the carbon-p2-plugin. Underneath the p2-plugin we invoke the p2-director to generate above structure.
We can do this by passing below arguments to generate separate profile configuration folder (-destination) and share the same p2 data location (-shared) by all the Profiles.

Arguments to invoke P2-director;

-metadataRepository : metadataRepository.toExternalForm() 
-artifactRepository : metadataRepository.toExternalForm()
-installIU : installIUs
-profileProperties :  org.eclipse.update.install.features=true 
-destination : ${destination}/${profile}
-bundlepool : ${destination}
-shared : ${destination}/p2
-profile :  ${profile}
-roaming

To have a clear understanding of what each of above argument is used for, please refer the p2-director documentation.

eg: 
-nosplash -application org.eclipse.equinox.p2.director -metadataRepository file:/dileepa/kernel/trunk/distribution/product/modules/p2-profile-gen/target/p2-repo -artifactRepository file:/dileepa/kernel/trunk/distribution/product/modules/p2-profile-gen/target/p2-repo -profileProperties org.eclipse.update.install.features=true -installIU org.wso2.carbon.core.feature.group/4.1.0.SNAPSHOT,org.wso2.carbon.tryit.feature.group/4.1.0.SNAPSHOT,org.wso2.carbon.soaptracer.feature.group/4.1.0.SNAPSHOT,org.wso2.carbon.styles.feature.group/4.1.0.SNAPSHOT, -bundlepool /dileepa/kernel/trunk/distribution/product/modules/p2-profile-gen/target/wso2carbon-core-4.1.0-SNAPSHOT/repository/components -shared /dileepa/kernel/trunk/distribution/product/modules/p2-profile-gen/target/wso2carbon-core-4.1.0-SNAPSHOT/repository/components/p2 -destination /dileepa/kernel/trunk/distribution/product/modules/p2-profile-gen/target/wso2carbon-core-4.1.0-SNAPSHOT/repository/components/WSO2SampleProfile -profile WSO2SampleProfile -roaming

In the POC done for this, in carbon-p2-plugin I have modified the MaterialzeProductMojo and ProfileGenMojo classes by passing above arguments to the p2-director to support multiple-profile installations with a shared p2 area.

What I basically did was, changing the values for the below parameters in addArguments() method;
"-destination", destination + File.separator + profile
"-shared" , destination + File.separator + "p2"

With -destination set like above, a separate directory will be created to hold the Eclipse configuration directory for the Profile(eg: ${carbon.home}/respository/components/WSO2CarbonProfile/configuration, ${carbon.home}/respository/components/WSO2SampleProfile/configuration)

And in the separate configuration folder we will have the config.ini and org.eclipse.equinox.simpleconfigurator/bundles.info file which has the list of all the bundles to start, for this particular runtime represented by the P2 profile. 

Note: 
Since we are changing the Product distribution structure by changing install-root to point to /repository/components/${Profile}, the relative-paths for copying files during feature installation should be changed accordingly in the Platform features. We copy various configuration files during feature installations, and this this done using P2 touchpoints instructions (org.eclipse.equinox.p2.touchpoint.natives.copy touchpoint is used here).
In Carbon features we have a p2.inf file to define these instructions and the new relative paths from install directory should be changed as below;

eg:
org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.logging.mgt.server_${feature.version}/conf/logging-config.xml,target:${installFolder}/../../conf/etc/logging-config.xml,overwrite:true);  

After making changes to features which copy files at installation and creating the product distribution like above the next thing to look at is, how we are going to launch the Equinox runtime to point to different P2 profiles, at Carbon startup.

2. How to manage the Multi-Profile Carbon runtime

Equinox finds the necessary files and directory locations to start and load the Equinox runtim using some parameters.

Some of the important parameters are below; 

1. osgi.install.area  (the root of a Eclipse product installation)
2. osgi.configuration.area (the configuration folder of an Eclipse product)
3. eclipse.p2.data.area (the P2 data area of the Profile)

So when we select a particular Profile, at startup by passing a parameter (eg: -Dprofile=WSO2SampleProfile), we need to pass above parameters based selected Profile.  Basically we need to point Equinox to the relevant Profile root and configuration directories at startup. And if a profile is not given at startup, Carbon should start with the default WSO2CarbonProfile. 
Please note that the default WSO2CarbonProfile is passed as a system property at carbon.server.Main class if no profile is given at startup. 

So the changing parameter values are
osgi.install.area : ${carbon.home}/respository/components/${profile}
osgi.configuration.area :  ${carbon.home}/respository/components/${profile}/configuration
Please see how these parameters are passed in the POC's modified carbon.server CarbonLauncher class.

So with above changes we can successfully start different Carbon profiles from a pre-installed multi-profile distribution.   

Things to improve and fix 

1. Feature installation with copying resources at runtime

The feature installation at runtime have issues with features having various files to copy, during installation. The problem is again with relative-paths to find /features directory.
If we generate profiles with -roaming enabled (which allows a Eclipse product to be moved) at runtime P2 updates the bundle-pool location to point to the same directory as the p2.installFolder.  Because of this, when we install features at runtime, /features and /plugins directories will be created inside ${carbon.home}/repository/components/${profile}

In P2 the location of the installFolder is defined by org.eclipse.equinox.p2.installFolder and the location of the bundle.pool is calculated using org.eclipse.equinox.p2.cache property. At runtime P2 engine updates the p2.cache property to point to the installFolder and the /features and /plugins are extracted there. 

eg: 
At build-time when we invoke p2-director the bundle-pool location is created at ${carbon.home}/repository/components/features
${carbon.home}/repository/components/plugins

But at runtime when we install  features it will be extracted under the installFolder as below;
${carbon.home}/repository/components/WSO2SampleProfile/features
${carbon.home}/repository/components/WSO2SampleProfile/plugins

So the same file-paths we changed in p2.inf above will be invalid and feature installation will fail during copy instruction execution.
To fix this issue, we need to either find a way to override the p2.cache location at runtime to point to the bundle-pool at ${carbon.home}/repository/components/features, /plugins
Or use some other way to derive the file-path in the p2.inf to copy files during feature installations.

2. Improve carbon-p2-plugin to a have a new goal to merge product materializing and profile-gen goals so that new Profiles can be generated in single execution without iterating the process of materializing the product and installing features afterwards.