Skip to end of metadata
Go to start of metadata

WSO2 App Factory supports the development of several runtimes for enterprise applications, such as Java Web applications, JAX-RS and JAX-WS services, Jaggery applications, dataservices etc. The App Factory has extension points that allow you to add any app type that is not available by default. 

In this guide, we explain how to create a new app type for PHP applications and PHP runtime environment in the App Factory. The runtime environment is set up as a IaaS (Infrastructure as a Service) image in a Cloud service such as OpenStack or Amazon EC2. After setting up, every time the user creates a PHP application in the App Factory, an instance of the IaaS image is spawned in the Cloud, providing the user the necessary runtime to build and deploy PHP applications. 

We use Apache Stratos as the PaaS (Platform as a Service) framework that provides the environment for the App Factory to work with the Cloud. It facilitates spawning of the IaaS image in the Cloud, load balancing, auto scaling, multi-tenancy etc. Stratos manages applications using a concept called Stratos cartridges. 

cartridge is a virtual machine (VM) image on an IaaS that has software components to interact with Apache Stratos PaaS. We have Apache Stratos integrated with the App Factory by default, and we also set up a Stratos cartridge agent in the IaaS image in the Cloud. 

A cartridge agent  is a component that resides within a cartridge instance of Stratos and handles the communication between the cartridge and Stratos. The cartridge agent manages the cartridge's lifecycle, synchronises the deployment artifacts and communicates statistics about the cartridge. For more information about Apache Stratos, see the Apache Stratos documentation.

Let's see how to create a new app type, its runtime environment as a IaaS image and how to set up Apache Stratos to enable the App Factory to work with the IaaS image that facilitates the runtime: 

Creating a new app type 

You can add a new app type to the App Factory by deploying an archive file as described in the following steps.

  1. Create a Java class for your new app type by implementing the interface org.wso2.carbon.appfactory.core.apptype.ApplicationTypeProcessor. If it is already there in the existing application processor classes, you don't have to write it from scratch. In this example, we use the following class for PHP:

    PHPApplicationTypeProcessor.java
    package org.wso2.carbon.appfactory.application.mgt.type;
    import org.apache.axiom.om.OMElement;
    import org.apache.commons.io.FileUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.wso2.carbon.appfactory.common.AppFactoryConfiguration;
    import org.wso2.carbon.appfactory.common.AppFactoryConstants;
    import org.wso2.carbon.appfactory.common.AppFactoryException;
    import org.wso2.carbon.appfactory.common.util.AppFactoryUtil;
    import org.wso2.carbon.appfactory.core.apptype.ApplicationTypeManager;
    import org.wso2.carbon.appfactory.core.dao.JDBCApplicationDAO;
    import org.wso2.carbon.appfactory.core.dto.CartridgeCluster;
    import org.wso2.carbon.appfactory.core.util.AppFactoryCoreUtil;
    import org.wso2.carbon.appfactory.s4.integration.StratosRestService;
    import org.wso2.carbon.appfactory.utilities.project.ProjectUtils;
    import org.wso2.carbon.appfactory.utilities.version.AppVersionStrategyExecutor;
    import org.wso2.carbon.context.CarbonContext;
    import java.io.File;
    import java.util.Map;
    
    /**
     * PHP application type processor
     */
    public class PHPApplicationTypeProcessor extends AbstractFreeStyleApplicationTypeProcessor {
        private static final Log log = LogFactory.getLog(PHPApplicationTypeProcessor.class);
        private static final String PARAM_CARTRIDGE_IP = "{cartridgeIP}";
        private static final String ENVIRONMENT = "ApplicationDeployment.DeploymentStage";
        private static final String TENANT_MANAGEMENT_URL = "TenantMgtUrl";
        private static final String SYMBOL_AT = "@";
    
        @Override
        public void generateApplicationSkeleton(String applicationId, String workingDirectory) throws AppFactoryException {
            ProjectUtils.generateProjectArchetype(applicationId, workingDirectory,
                    ProjectUtils.getArchetypeRequest(applicationId,
                            getProperty(MAVEN_ARCHETYPE_REQUEST)));
            File pomFile = new File(workingDirectory + File.separator + AppFactoryConstants.DEFAULT_POM_FILE);
            boolean result = FileUtils.deleteQuietly(pomFile);
            if (!result){
                log.warn("Error while deleting pom.xml for application id : " + applicationId);
            }
        }
        @Override
        public String getDeployedURL(String tenantDomain, String applicationId, String applicationVersion, String stage)
                throws AppFactoryException {
            String deployedUrl = StringUtils.EMPTY;
            try {
                String urlPattern = (String) this.properties.get(LAUNCH_URL_PATTERN);
                String artifactTrunkVersionName = AppFactoryUtil.getAppfactoryConfiguration().
                        getFirstProperty(ARTIFACT_VERSION_XPATH);
                String sourceTrunkVersionName = AppFactoryUtil.getAppfactoryConfiguration().
                        getFirstProperty(SOURCE_VERSION_XPATH);
                if (sourceTrunkVersionName.equalsIgnoreCase(applicationVersion)) {
                    applicationVersion = artifactTrunkVersionName;
                }
                // get cartridge active IP
                String cartridgeActiveIp = getCartridgeActiveIP(applicationId, tenantDomain, stage);
                // generate application url
                if (StringUtils.isNotBlank(urlPattern) && StringUtils.isNotBlank(cartridgeActiveIp)) {
                    urlPattern = urlPattern.replace(PARAM_CARTRIDGE_IP, cartridgeActiveIp)
                            .replace(PARAM_APP_ID, applicationId)
                            .replace(PARAM_APP_VERSION, applicationVersion);
                    deployedUrl = urlPattern;
                }
            } catch (AppFactoryException e) {
                if (log.isErrorEnabled()) {
                    log.error("Error while generating application url !", e);
                }
            }
            return deployedUrl;
        }
        private String getCartridgeActiveIP(String applicationId, String tenantDomain, String stage)
                throws AppFactoryException {
            AppFactoryConfiguration configuration = AppFactoryUtil.getAppfactoryConfiguration();
            String serverURL = configuration.getFirstProperty(ENVIRONMENT + XPATH_SEPERATOR + stage + XPATH_SEPERATOR +
                    TENANT_MANAGEMENT_URL);
            String userName = CarbonContext.getThreadLocalCarbonContext().getUsername() + SYMBOL_AT +
                    CarbonContext.getThreadLocalCarbonContext().getTenantDomain();
            String cartridgeAlias = AppFactoryCoreUtil.getCartridgeAlias(applicationId, tenantDomain);
            StratosRestService restService = new StratosRestService(serverURL, userName, StringUtils.EMPTY);
            String clusterId = restService.getSubscribedCartridgeClusterId(cartridgeAlias);
            // get data from runtime db
            JDBCApplicationDAO jdbcApplicationDAO = JDBCApplicationDAO.getInstance();
            CartridgeCluster cartridgeCluster = jdbcApplicationDAO.getCartridgeClusterByClusterId(clusterId);
            if(cartridgeCluster != null) {
                return cartridgeCluster.getActiveIP();
            }
            return null;
        }
    } 
  2. Build the above Java class and save the JAR file in <AF_HOME>/repository/components/lib.

  3. Create the configuration file of the app type. It adds the new app type to the App Factory UIs, and defines the new app type's extension, whether the app type is buildable or not etc. For example,

    apptype.xml
    <ApplicationType>
        <ApplicationType>php</ApplicationType>
        <ProcessorClassName>org.wso2.carbon.appfactory.application.mgt.type.PHPApplicationTypeProcessor</ProcessorClassName>
        <DisplayName>PHP Web Application</DisplayName>
        <Extension></Extension>
        <Description>php web application</Description>
        <Buildable>true</Buildable>
        <BuildJobTemplate>freestyle</BuildJobTemplate>
        <MavenArcheTypeRequest>-DarchetypeGroupId=org.wso2.appfactory
                -DarchetypeArtifactId=php-archetype -DarchetypeVersion=2.1.0-SNAPSHOT -DgroupId=org.wso2.appfactory
                -Dversion=default-SNAPSHOT
                -DinteractiveMode=false -DarchetypeCatalog=local
        </MavenArcheTypeRequest>
        <ServerDeploymentPaths>phpapps</ServerDeploymentPaths>
        <Enable>enabled</Enable>
        <Comment>Test</Comment>
        <Language>PHP</Language>
        <SubscriptionAvailability>aPaaS</SubscriptionAvailability>
        <IsUploadableAppType>false</IsUploadableAppType>
        <DeployerType>php</DeployerType>
        <LaunchURLPattern>http://{cartridgeIP}/phpapps/{applicationID}-{applicationVersion}</LaunchURLPattern>
        <Runtimes>php</Runtimes>
        <ExecutionType>Open</ExecutionType>
        <IsCodeEditorSupported>true</IsCodeEditorSupported>
    </ApplicationType>

    The elements of the above configuration are described below:

    ElementDescriptionData typeFixed value/s
    <ApplicationType>

    The type of the application archive. Currently, the App Factory supports Web applications (war), JAX-RS (jaxrs), JAX-WS (jaxws), WSO2 BPS (bpel), Data Services (dbs) and WSO2 Jaggery (jaggery).

    You can specify a new type, but has to be related to the supported types.
    String 
    <ProcessorClassName>Class name of the application implementation typeString 
    <DisplayName>

    Display name of the application type. You see this name under the Application Type drop-down list when you create a new application.

    String 
    <Extension>

    Extension of the application's archive type. The App Factory supports the following types and archive names:

    • Web Application: war

    • JAX-RS: war

    • JAX-WS: war

    • Data Services: dbs

    • WSO2 Jaggery: zip
    Stringwar, zip, dbs
    <Description>Detailed description of the application typeString 
    <Buildable>If the artifact is not buildable, set this property to false. It will then not go through the process of being built on the build server.Booleantrue/false
    <BuildJobTemplate>The build job configuration template name for the Jenkins application build.  
    <MavenArcheTypeRequest>Maven archive type request for the build. Change the archive type based on the application type. The existing types can be found in github.  
    <ServerDeploymentPaths>Server deployment path  
    <Enable>Defines whether the application type is enabled or not.StringEnabled/Disabled
    <Language>Programming language used to write the artifact's code.  
    <IsUploadableAppType>The application can be uploaded to the App Factory as an archive file. See here.  
    <DevelopmentStageParam>The value you define here will be added to the URL pattern of the application when it is in the development stage.  
    <TestingStageParam>The value you define here will be added to the URL pattern of the application when it is in the testing stage.  
    <LaunchURLPattern>Defines the URL patten of the artifact that is deployed. For example, if you define the URL as https://appserver{stage}.appfactory.private.wso2.com:9443, then the <stage> will dynamically change in each lifecycle stage according to the values you define in the <DevelopmentStageParam> and <TestingStageParam> elements.  
    <DisplayOrder>The place in which the new application type will appear in the application type drop-down list in the App Factory console.  
    <IsAllowDomainMapping>Whether to allow domain mapping for this application type or notBooleantrue/false
    <Runtimes>The supported runtimes as a comma-separated list (only a single runtime is supported at the moment)String 

    Tip: Based on the build perspective of apps, they can be categorized as follows. 

    • Buildable app types that need to be built to get the artifact for deploying the application. E.g., Java, .Net

    • Freestyle app types that do not need to be built to be deployed. E.g, Jaggery, PHP

    The PHP app type that you create here is freestyle but if you create a buildable app type, you must create a Jenkins build configuration for it. Jenkins is the default build server of the applications in the App Factory. Here's a sample build configuration for .Net.

  4. Create an archive of apptype.xml with the extension ‘apptype’ using the following command. If your app type is buildable, you must archive the build configuration also with this.

    zip -r <apptype-name>.apptype apptype.xml
  5. Save the archive file in <AF-HOME>/repository/deployment/server/apptypes/.

You have now created a new app type in the App Factory. If you log in to the App Factory console, you will be able to see it populated in the Application Type drop-down list when creating a new application.

Creating a runtime for the app type

To deploy applications of the new app type that you created earlier, you need to configure a runtime environment with the operating system and the necessary software installed. This runtime environment is similar to an instance of WSO2 App Server, IIS, PHP etc. set up on a Cloud service such as OpenStack or Amazon EC2. 

In this guide, you create a cartridge using a puppet-based cartridge configuration. The advantage of configuring cartridges using Puppet is that DevOps will be able to configure new cartridge instances or reconfigure existing ones without changing the virtual machine image.

Puppet is a configuration management system that allows you to define the state of your IT infrastructure and automatically enforce the correct state and automate other sysadmin tasks. In Apache Stratos, Puppet is used to automate the configuration process of all cartridges. In a Stratos deployment, you set up a puppet master with the puppet scripts for setting up cartridges, cartridge software and the cartridge agent.

When a cartridge starts, a startup script (e.g., init.sh) invokes the puppet agent. The puppet agent then communicates with the puppet master, which automatically configures the respective cartridge based on the SERVICE_NAME payload parameter that defines the service type. For example, if SERVICE_NAME=PHP, the puppet agent communicates with the puppet master and configures the cartridge to host PHP applications. For more information about puppet based configurations, see Apache Stratos documentation.

  1. Create a virtual image of an operating system. As you set up a PHP runtime here, Ubuntu is recommended.

  2. Execute the following commands on the VM instance:

    TaskCommand

    Login to the instance using SSH and get root access to the instance

    sudo su
    Install zip and unzip
    apt-get install zip unzip
    Create a new directory named bin in the home directory
    mkdir ~/bin
    Save the config.sh in the ~/bin directory and make it executable. As this is a puppet-based configuration, the config.sh script is used to install the puppet agent to the instance. This puppet agent communicates with the puppet master and configures the required environment using puppet scripts. You configure the puppet master at the time the App Factory is installed. The config.sh script also configures the init.sh file to be executed automatically during the startup of the instance.
    chmod +x config.sh
    Save the init.sh in the ~/bin directory and make it an executable. 

    This init.sh script,

    • Starts the puppet agent
    • The cartridge agent needs several parameters from the Amazon EC2 metadata service as payload to communicate with the Stratos manager in the App Factory. The metadata service is used for virtual machine instances to retrieve instance-specific data. It supports two sets of APIs: an OpenStack metadata API and an EC2-compatible API. Instances access the metadata service on http://169.254.169.254
      Downloads the launch parameters from the Amazon EC2 metadata service using the following command: wget http://169.254.169.254/latest/user-data -O /tmp/payload/launch-params. 

    • Configurs the host file of the instance with the required host entries
    • Downloads the cartridge agent from the puppet master in the App Factory
     
    chmod +x init.sh
    Save the stratos_sendinfo.rb file in the ~/bin directory. 
    Create a directory named puppetinstall inside the ~/bin directory.mkdir ~/bin/puppetinstall
    Save the puppetinstall file in ~/bin/puppetinstall/ and make it an executable.chmod +x ~/bin/puppetinstall/puppetinstall
    In the above script file, set the TIMEZONE value that matched the timezone set in the puppet master using the following command. In this case, we are setting the timezone to Etc/UTC.sed -i 's:^TIMEZONE=.*$:TIMEZONE=\"Etc/UTC\":g' ~/bin/puppetinstall/puppetinstall
    Create a directory named apache-stratos-cartridge-agent in /mnt/
    mkdir /mnt/apache-stratos-cartridge-agent/

    Save the cartridge-agent.sh file in /mnt/apache-stratos-cartridge-agent/ and make it an executable. This script is used to configure and start the cartridge agent.

    chmod +x /mnt/apache-stratos-cartridge-agent/cartridge-agent.sh
    Execute the config.sh file using the following command:
    ~/bin/config.sh

    Provide the following details when prompted:

    This script will install and configure puppet agent, do you want to continue [y/N] y
    Please provide stratos service-name: php
    Please provide puppet master IP: <IP-Of-AppFactoryServer>
    Please provide puppet master hostname [puppet.test.org]: puppet.test.org
  3. In OpenStack, create a snapshot image of the runtime instance that you configured in the IaaS. Note that an ID is generated for the snapshot. We use that in step 5 below.

  4. Add this runtime configuration to the App Factory by saving it as an XML file in <AF_HOME>/repository/deployment/server/runtimes/. As hot deployment is enabled, it will be deployed automatically. 

    <Runtime>
        <Runtime>php</Runtime>
        <DeployerClassName>org.wso2.carbon.appfactory.jenkins.deploy.JenkinsArtifactDeployer</DeployerClassName>
        <PAASArtifactStorageURLPattern>{@stage}/as</PAASArtifactStorageURLPattern>
        <AliasPrefix>{@appName}</AliasPrefix>
        <CartridgeTypePrefix>php</CartridgeTypePrefix>
        <DeploymentPolicy>af-deployment</DeploymentPolicy>
        <AutoscalePolicy>economy</AutoscalePolicy>
        <SubscribeOnDeployment>true</SubscribeOnDeployment>
        <UndeployerClassName>org.wso2.carbon.appfactory.jenkins.deploy.JenkinsArtifactUndeployer</UndeployerClassName>
    </Runtime>

    The elements of the above configuration are described below:

    ElementDescriptionData typeDefault value
    <Runtime>Name of the runtime that you wish to addString 
    <DeployerClassName>The fully qualified class name of the application deployerStringDefault is org.wso2.carbon.appfactory.jenkins.deploy.JenkinsArtifactDeployer
    <PAASArtifactStorageURLPattern>

    The artifact storage URL where the application artifact is stored. The URL is resolved using the stage of the application in the runtime.

    {@stage}- resolves the current stage of the application (e.g., Development, Testing, Production etc.) as the runtime prefix.
    String

    <@stage>/as

    'as' stands for Application Server.
    <AliasPrefix>Prefix of the aliasString 
    <CartridgeTypePrefix>Prefix of the cartridge typeString 
    <DeploymentPolicy>Deployment policy used for subscriptionStringaf-deployment
    <AutoscalePolicy>Autoscale policy used for subscriptionStringeconomy
    <SubscribeOnDeployment>Whether subscription is required at the time of deployment or not.BooleanFalse
    <UndeployerClassName>Fully qualified class name of the application un-deployerString

    org.wso2.carbon.appfactory.deployers.

    JenkinsS tratosUndeployer

You have now created an virtual image of a PHP runtime. At the time users create new PHP applications in the App Factory, this virtual image is used to spawn new PHP runtime instances in the Cloud environment. Next, let's configure Apache Stratos as the PaaS environment for the App Factory to work with the Cloud instance.

Configuring Apache Stratos in the App Factory

Apache Stratos is an extensible PaaS framework that helps you run Apache Tomcat, PHP, and MySQL applications. You can extend it to support many more environments on all popular Cloud infrastructures. For developers, Stratos provides a cloud-based environment for developing, testing, and running scalable applications.

  1. Apache Stratos is set up at the time you install the App Factory. Log in to the Stratos manager consoles of all three stratos instances (dev, test, prod) as admin, using the following URLs.

  2. In the Stratos manager console, go to Configure Stratos > Partitions and add the partition definition. Following JSON object shows a sample partition definition.

    {
             "id": "P1",
             "provider": "openstack",
             "property": [ {
                     "name": "region",
                     "value": "RegionOne"
             } ]
    }

     

  3. Go to Configure Stratos > Autoscale policies in the Stratos manager console and add the autoscale policy. This policy defines how to scale up and down the resources of your Cloud instance depending on different conditions, demand and load. The following JSON object shows a sample autoscale policy.

    {
         "id": "economy",
         "loadThresholds": {
              "requestsInFlight": {
              "average": "300",
              "gradient": "10",
              "secondDerivative": "0.2"
               },
              "memoryConsumption": {
                "average": "80",
               "gradient": "10",
             "secondDerivative": "0.2"
           },
           "loadAverage": {
             "average": "600",
             "gradient": "20",
             "secondDerivative": "0.2"
          }
         }
    }
  4. Similarly, go to Configure Stratos > Deployment policies and add the deployment policy. The following JSON object shows a sample deployment policy.

    {
       "id": "deployp",
       "partitionGroup": {
         "id": "openstack",
         "partitionAlgo": "one-after-another",
         "partition": [
           {
             "id": "P1",
             "partitionMax": "10",
             "partitionMin": "2"
           }
         ]
       }
    }

     

  5. Go to Configure Stratos > Cartridges and add the cartridge definition. The following JSON object shows a sample cartridge definition for development environment.

    • Please change the placeholders with actual values. 
    • In this configuration, the cartridge type should be the concatenation of the CartridgeTypePrefix of the runtime and the environment type of the cartridge. The CartridgeTypePrefix is an element of the runtime configuration defined below. For example, if the CartridgeTypePrefix is Windows, then the cartridge type of development environment is windowsdevelopment.
    • The ImageId should be the ID of the image that was created when creating a snapshot image of the runtime in the IaaS. 
    {
        "type": "php",
          "provider": "apache",
          "host": "stratos.org",
          "displayName": "PHP",
          "description": "PHP Cartridge",
          "version": "7",
          "multiTenant": "false",
          "portMapping": [
             {
                "protocol": "http",
                "port": "80",
                "proxyPort": "8280"
             },
             {
                "protocol": "https",
                "port": "443",
                "proxyPort": "8243"
             }
           ],
        "deployment":{
        }, 
        "iaasProvider":[
            {
                "type":"openstack",
                "imageId":"RegionOne/58746cf5-2d59-4a6b-9e7c-9a580c8a242d",
                "maxInstanceLimit":"4",
                "property":[
                    {
                        "name": "instanceType",
                 	    "value": "RegionOne/3"
                    },
                    {
                        "name":"keyPair",
                        "value":"appfackey"
                    }
                ]
            }
        ]
    }
  • No labels