Monday, August 25, 2014

Beginning to understand BPEL

Beginning to understand BPEL

I'm beginning to learn Business Process Execution Language, BPEL, which is the language used to configure workflows in WSO2 BPS. It also is the successor to Business Process Markup Language (BPML), which I'm familiar with from the IBM Gentran / Sterling B2B Integration software.

Comparing the two languages, BPEL is much more evolved, and includes a great deal more information. Especially because the BPEL is intimately linked to WSDLs, the configuration files contain much more information that a BPML. The BPML includes mostly workflow instructions without the web-service components of BPEL. That said, it is a struggle to understand the complex structure of BPEL. This blog post is an attempt to encapsulate what I've learned from studying it over the last few days.

Multiple layers of references

There are multiple layers of references inside a BPEL to invoke a service defined in a WSDL. I count eight layers of self-references within and between a BPEL file and WSDL to invoke a service. It's difficult to understand, and I must credit Dennis Weerasiri for an excellent blog post hosted in the WSO2 library. Without that, I wouldn't know where to start in understanding BPEL. In order to easily refer to the different 'layers', I've named them. Here are the names, numbers and xpath for the layers. I'm using the same sample files Dennis used, and they are available for download from the WSO2 library.

1. NameSpaceDefinition
//@xmlns:ns0
2. Import
//import[@namespace="http://AdderService.wsdl"] 
3. Variables
//variable[@name="SquareOutput"|@name=”SquareInput] 
4. Invoke
//invoke[@name="InvokeAdderService"] 
5. PartnerLink
//partnerLink[@name="AdderPartnerLink"] 
6. PartnerLinkType (in wsdl)
//plink:partnerLinkType[@name="AdderServicePartnerlinkType"] 
7. PortType (in wsdl)
//wsdl:portType[@name="AdderServicePortType"]

The Head Bone is connected to the Tail Bone

All these levels are connected by attributes. Here are examples of the elements which connect each level:

1 → 2 The NameSpaceDefinition's address is specified in the Import.
1 → 3, 4, and 5 The NameSpaceDefinition's prefix is used in the Variables, Invoke and PartnerLink.
2 → 6, and 7 The location of the wsdl file is specified explicitly in the Import.
3 → 4 The input and output of the Invoke statement is specified in the Variables.
3 → 7 The operation attribute of the Invoke statement is the wsdl:operation in the PortType
4 → 5 The partnerLink attribute in the Invoke is the name attribute in the PartnerLink
5 → 6 The partnerRole attribute in the PartnerLink is the plink:role name in the PartnerLinkType
6 → 7 The portType attribute of the PartnerLinkType is the name attribute of the PortType


Is that clear to me? Not at all. The links between levels demonstarte the complexity inherent in the BPEL structure. However, the fact that the configuration for a business process including web services is defined in just a few files is remarkable. I have a hunch that the BPEL will be useful in defining AS2 relationships, which is a cornerstone for EDI communication.


 Cheers, Y'all!

Wednesday, August 13, 2014

Clustered WSO2 API Manager with Elastic Load Balancer and Identity Server (WSO2 AM + ELB + IS)

WSO2 has many products that can be combined flexibly to scale and meet business needs.  There is extensive and useful documentation about clustering here.  However, this doesn't cover each and every scenario, because there are so many possible combinations.  In this post, I will outline how to set up a distributed API Manager (AM) with the Identity Server (IS) as a Key Manager and a worker/manager Gateway cluster fronted by the Elastic Load Balancer (ELB).  This setup also allows tenanting.

Architecture

Before I describe how to install or configure the products, I need to lay out the architecture I plan to use, defining nodes, roles, clusters, ports, databases, domain and host names.

For this exercise, I will set up all the products on a single machine running Linux Mint.  Each product will each have a distinct port offset.  Wherever possible, I will use host names instead of IPs to refer to other nodes.  This requires some customization of /etc/hosts.

This configuration uses five distinct AM servers, in blue. The basic idea of API Manager separation into these roles is well documented here.  Adding an Identity Server as a Key Manger is documented here.  Worker-manager separation with a load balancer is covered here. If you only need to apply one of these concepts, please follow the existing documentation.  Otherwise, please continue!

Versions

I'm using the Carbon 4.2.0 platform, with
WSO2 AM 1.7.0
WSO2 IS 5.0.0
WSO2 ELB 2.1.1


HTTP Ports and Offsets

Each instance installed on a single machine must have a distinct port offset.  This creates some complications, so I want to be very clear with the port definitions up-front.  First of all, there are four HTTP ports for each AM instance, including management HTTP/HTTPS ports and non-blocking HTTP-NIO (also called NHTTP and NHTTPS) ports.  To learn about the non-blocking NIO technology, you can read more on the Apache site or in the WSO2 documentation.  For more information on WSO2 products and ports, see the documentation here.

Clustering Ports

Clustering ports are distinct from the ports used for http. Note that the cluster ports here have nothing to do with the http port offsets.  Also, the Store and Publisher are in a cluster together, as shown by the domain.  I'm not setting up clustering for the Identity server here.  I'm also not using the ELB to front the Store or Publisher.

Host Names

I will use host names for individual servers, and there are also host names used by the ELB for the clustered groups (worker and manager).  The red arrows below define how the host name is used to pass messages through the load balancer.  These host names will need to be added to /etc/conf. Please note that the host names for the load-balanced groups will need to route to the load balancer via etc/conf or another means, and the load balancer will forward these on to the clustered group members.

Databases

Instead of using only the default H2/Cassandra databases, I'm using a local MySQL db. There are three databases here, which are referred to by either a jndi Config name, host name and/or the MySQL name:



MySQL DB Name jndi Config Name Host Name
userstore jdbc/WSO2UM_DB userdb.mysql-wso2.com
registry jdbc/WSO2REG_DB regdb.mysql-wso2.com
apimgt jdbc/WSO2AM_DB apimgtdb.mysql-wso2.com

The apimgt DB can be connected to the Store, Publisher and IS only, without connecting to the gateway nodes.  The other two DBs can be connected to all the nodes except the ELB.  In some security situations, you may wish to further restrict which nodes have access to which databases, but I'll keep it a little simple here.  In production settings where the gateway nodes are outside the firewall, a different configuration would be necessary. 

Database Connectivity

Installation

In this case, I will create a folder structure like /home/username/wso2/AM_IS_ELB/ and create 7 sub-folder directories, one for each server I'm installing.  You can name these whatever you like.  Download the source from WSO2 and extract the zipped contents to the relevant directories.  I will refer to these directories like this through the rest of this post:  <Load Balancer Home>.

Configuration

carbon.xml

Open /repository/conf/carbon.xml on for the Gateway Manager, Gateway workers, Store, Publisher and Identity Server.  Change the port offset for each node. 

1. Gateway Manager,   <Offset>1</Offset>
2. Gateway Worker1,   <Offset>2</Offset>
3. Gateway Worker2,   <Offset3</Offset>
4. Store,   <Offset>4</Offset>
5. Publisher,   <Offset>5</Offset>
6. IS,   <Offset>10</Offset>
7. ELB, no change necessary. 

Adding Features to the Identity Server

Before continuing with configuration, we need to add a few additional features to the Identity server.  This is outlined well in the WSO2 documentation, from steps 3.a to 3.c.iii. .  Don't continue to step 3.c.iv .

Database Setup

For setting up the databases, please refer to steps 1 through 3 in the 'Installing and configuring the Databases' here.  Then, follow steps 8 and 9 here.  Don't forget to copy over the MySQL jdbc driver to each node.

master-datasources.xml

Open /repository/conf/datasources/master-datasources.xml for the Gateway Manager, Gateway workers, Store, Publisher and Identity Server.   For the Gateway Manager, Gateway Worker1 and Gateway Worker2, add the following two datasources:

<datasource>
    <name>WSO2REG_DB</name>
    <description>The datasource used for registry and user manager</description>
    <jndiConfig>
        <name>jdbc/WSO2REG_DB</name>
    </jndiConfig>
    <definition type="RDBMS">
        <configuration>
<url>jdbc:mysql://regdb.mysql-wso2.com:3306/registry?autoReconnect=true&amp;relaxAutoCommit=true&amp;</url>
            <username>apiuser</username>
            <password>apimanager</password>
            <driverClassName>com.mysql.jdbc.Driver</driverClassName>
            <maxActive>50</maxActive>
            <maxWait>60000</maxWait>
            <testOnBorrow>true</testOnBorrow>
            <validationQuery>SELECT 1</validationQuery>
            <validationInterval>30000</validationInterval>
        </configuration>
    </definition>
</datasource>
<datasource>
    <name>WSO2UM_DB</name>
    <description>The datasource used for registry and user manager</description>
    <jndiConfig>
        <name>jdbc/WSO2UM_DB</name>
    </jndiConfig>
    <definition type="RDBMS">
        <configuration>
   <url>jdbc:mysql://userdb.mysql-wso2.com:3306/userstore?autoReconnect=true&amp;relaxAutoCommit=true&amp;
            </url>
            <username>apiuser</username>
            <password>apimanager</password>
            <driverClassName>com.mysql.jdbc.Driver</driverClassName>
            <maxActive>50</maxActive>
            <maxWait>60000</maxWait>
            <testOnBorrow>true</testOnBorrow>
            <validationQuery>SELECT 1</validationQuery>
            <validationInterval>30000</validationInterval>
        </configuration>
    </definition>
</datasource>

For the Store, Publisher and Identity Server master-datasources.xml, first add the datasources above. Then comment out the existing entry for WSO2AM_DB and replace it with the new entry for WSO2AM_DB below.

        <datasource><name>WSO2AM_DB</name>
            <description>The datasource used for API Manager database</description>
            <jndiConfig>
                <name>jdbc/WSO2AM_DB</name>
            </jndiConfig>
            <definition type="RDBMS">
                <configuration>
                    <url>jdbc:mysql://apimgtdb.mysql-wso2.com:3306/apimgt?autoReconnect=true&amp;relaxAutoCommit=true&amp;</url>
                    <username>apiuser</username>
                    <password>apimanager</password>
                    <driverClassName>com.mysql.jdbc.Driver</driverClassName>
                    <maxActive>50</maxActive>
                    <maxWait>60000</maxWait>
                    <testOnBorrow>true</testOnBorrow>
                    <validationQuery>SELECT 1</validationQuery>
                    <validationInterval>30000</validationInterval>
                </configuration>            </definition>
<        </datasource>
Now that the datasources are set up, we can use the jndiConfig name in other property files to connect to the databases.

user-mgt.xml

Open the /repository/conf/user-mgt.xml for the Gateway Manager, Gateway workers, Store, Publisher and Identity Server.   Change the datasource name to WSO2UM_DB:

<Realm>       
   ... 
   <Configuration>
     ...
     <Property name="dataSource">jdbc/WSO2UM_DB</Property>
     ... 
   </Configuration>
   ...
</Realm>    

registry.xml

Copy the registry.xml file from the <GATEWAY_MANAGER>/repository/conf directory to the <IS_HOME>/repository/conf directory. Make sure you replace the existing registry.xml file found in the <IS_HOME>. This is because the <indexingConfiguration> element is not there in the registry.xml that comes in the IS.

Open the /repository/conf/registry.xml for the Gateway Manager, Gateway workers, Store, Publisher, and Identity Server.  In each file, add this registry configuration:
<dbConfig name="govregistry">
       <dataSource>jdbc/WSO2REG_DB</dataSource>
</dbConfig>

<remoteInstance url="https://localhost">   
       <id>gov</id>
       <dbConfig>govregistry</dbConfig>
       <readOnly>false</readOnly>
       <enableCache>true</enableCache>
       <registryRoot>/</registryRoot>
</remoteInstance>

<mount path="/_system/governance" overwrite="true">
       <instanceId>gov</instanceId>
       <targetPath>/_system/governance</targetPath>
</mount>

<mount path="/_system/config" overwrite="true">
       <instanceId>gov</instanceId>
       <targetPath>/_system/config</targetPath>
</mount>

In addition, add the following code block to the <IS_HOME>/repository/conf/registry.xml :
  <handler class="org.wso2.carbon.identity.entitlement.policy.finder.registry.RegistryPolicyHandler">
     <filter class="org.wso2.carbon.identity.entitlement.policy.finder.registry.RegistryPolicyMediaTypeMatcher">
     <property name="mediaType">application/xacml-policy+xml</property></filter> </handler>

identity.xml

Open the identity.xml file in <IS_HOME>/repository/conf/. Change the datasource from the default carbonDB to the mySql WSO2AM_DB:
 <DataSource>
    ...         
    <Name>jdbc/WSO2AM_DB</Name>
    ... 
  </DataSource>

application-authentication.xml

Open the identity.xml file in <IS_HOME>/repository/conf/security/application-authentication.xml. Change the datasource from the default carbonDB to the mySql WSO2AM_DB:
 <DataSource>
          </handler>
          ...
          <Name>jdbc/WSO2AM_DB</Name>
          ... 
  </DataSource>

loadbalancer.conf

Open the <ELB_HOME>/repository/conf/loadbalancer.conf. Add the following section:

am {
        # multiple hosts should be separated by a comma.
    hosts                   am.cloud-test.wso2.com; 
        #url_suffix         am.wso2.com;
        domains   {
            am.gateway.domain {
                tenant_range    *;
        group_mgt_port 4500;
        worker {
            hosts am.wso2.com;
        }
        mgt {
            hosts mgt.am.wso2.com;
        }
            }
        }
    }

 api-manager.xml

Copy the api-manager.xml file from the <GATEWAY MANAGER HOME>/repository/conf/ directory to the  <IS_HOME>/repository/conf/
directory. Make sure you replace the existing api-manager.xml file found in the <IS_HOME>.  Open the newly copied <IS_HOME>/repository/conf/api-manager.xml and perform these edits:

    1. Change the GatewayType property to the following.
  <GatewayType>None</GatewayType>

    2. Change the <RevokeAPIURL> so that it points to the API Gateway Workers via the load balancer.
 <RevokeAPIURL>https://am.wso2.com:8243/revoke</RevokeAPIURL>

    3. Change the <ServerURL> occurring under the <APIGateway> section so that it points to the API Gateway Manager via the load balancer.
  <ServerURL>https://mgt.am.wso2.com:443/services/</ServerURL>

    4. Change EnableThriftServer to false.
 <EnableThriftServer>false</EnableThriftServer> 

Open the /repository/conf/api-manager.xml for the Gateway Manager, Gateway Workers, Store, and Publisher.  Perform the these edits:

     1. Change the ServerURL of the  AuthManager to point to IS.
  <ServerURL>https://is.wso2.com:9453/services/</ServerURL>

     2. Change the ServerURL of the APIKeyManager to point to IS.
  <ServerURL>https://is.wso2.com:9453/services/</ServerURL>

     3. Change the KeyValidatorClientType from ThriftClient to WSClient.

     4. Change EnableThriftServer to false.

In the Store and Publisher api-manager.xml, connect to the Gateway Manager node via the ELB.  Change the following URLs and Endpoints:

<APIGateway>    
  <Environments>
    <Environment type="hybrid">
      ...
      <ServerURL>https://mgt.am.wso2.com:9445/services/</ServerURL>
      ...
      <GatewayEndpoint>http://mgt.am.wso2.com:8280,https://mgt.am.wso2.com:8243</GatewayEndpoint>
    </Environment>
  </Environments>
  ...
</APIGateway>

axis2.xml

Open the <Load Balancer Home>/repository/conf/axis2/axis2.xml . Locate the Transport Ins (Listeners) section and change the PassThroughHttpListner to the default http ports as follows:
  <transportReceiver name="http" class="org.apache.synapse.transport.passthru.PassThroughHttpListener"> 
        ...
        <parameter name="port">80</parameter>
        ...
  </transportReceiver>
  <transportReceiver name="https" class="org.apache.synapse.transport.passthru.PassThroughHttpSSLListener">
         ...
         <parameter name="port" locked="false">443</parameter>
         ... 
  </transportReceiver>
Please note that the above changes to the default http/s listener ports will require that the Load Balancer be started with administrative access, using sudo or su.

Still in the same Load Balancer's axis2.xml file, in the Transport Ins (Listeners) section, add additional transports for 8280 and 8243 ports:

    <transportReceiver name="http" class="org.wso2.carbon.core.transports.http.HttpTransportListener">
         <parameter name="port">8280</parameter>
    </transportReceiver> 
    <transportReceiver name="https" class="org.wso2.carbon.core.transports.http.HttpsTransportListener">
         <parameter name="port">8243</parameter><parameter name="keystore" locked="false">
              <KeyStore>
                  <Location>repository/resources/security/wso2carbon.jks</Location>
                  <Type>JKS</Type>
                  <Password>wso2carbon</Password>
                  <KeyPassword>wso2carbon</KeyPassword>
              </KeyStore>
          </parameter>
          <parameter name="truststore" locked="false">
              <TrustStore>
                  <Location>repository/resources/security/client-truststore.jks</Location>
                  <Type>JKS</Type>
                  <Password>wso2carbon</Password>
              </TrustStore>
          </parameter> 
    </transportReceiver>
tory/conf/axis2/axis2.xml for the Gateway Manager, Gateway workers, Store and Publisher.  Each file will be configured differently:

1. Change the HazelcastClusteringAgent to enable="true"
    <clustering class="org.wso2.carbon.core.clustering.hazelcast.HazelcastClusteringAgent" enable="true">

2. Change the membershipScheme to wka
    <parameter name="membershipScheme">wka</parameter>

3. Change the domain to the appropriate clustering domain
   a. For the gateway manager and workers:
    <parameter name="domain">am.gateway.domain</parameter>
      b. For the Store and Publisher:
    <parameter name="domain">wso2.pub.store.domain</parameter>
      
4. Change the local member port to the pre-determined port, which is different for each node:

     <parameter name="localMemberPort">xxxx</parameter>
      a. Gateway Manager: 4001
      b. Gateway Worker1: 4002
      c. Gateway Worker2: 4003
      d. Store: 8011
      e. Publisher: 8010

5. Only for the Gateway Manager node, in the <Parameter name="Properties"> section, change the sub-domain to mgt
        <property name="subDomain" value="mgt"/> 


6. Add these Port Mappings to each node except the IS and ELB. They should be added in the <Parameter name="Properties"> section:

       a. Gateway Manager:
            <property name="port.mapping.80" value="9764"/>
            <property name="port.mapping.443" value="9444"/>
     b. Gateway Worker1:
            <property name="port.mapping.80" value="9765"/>
            <property name="port.mapping.443" value="9445"/>
     c. Gateway Worker2:
            <property name="port.mapping.80" value="9766"/>
            <property name="port.mapping.443" value="9446"/>
     d. Store:
            <property name="port.mapping.80" value="9767"/>
            <property name="port.mapping.443" value="9447"/>
     e. Publisher:
            <property name="port.mapping.80" value="9768"/>
            <property name="port.mapping.443" value="9448"/>

7. Replace the default <members> section with correct well-known members.  These include the cluster partners' IPs and clustering ports. Please note that this is the one place in this setup guide where it is necessary to use the actual IP of the servers.  This isn't a limitation of the WSO2 software, but it is an issue in the HazelCast cluster framework we use.  It should be fixed in the next versio<!-- note the use of the loopback IP -->n of HazelCast that WSO2 implements with the next version of Carbon.  Sorry for the inconvenience, but you should use your local IP here in place of xxx.xxx.xxx.xxx, and not just localhost or other host names.  Please use ifconfig or a similar tool to get your ip.

a. Gateway Manager:
        <members>
            <member>
                <hostName>xxx.xxx.xxx.xxx</hostName>
                <port>4500</port>
            </member>
            <member>
                <hostName>xxx.xxx.xxx.xxx</hostName>
                <port>4002</port>
            </member>
            <member>
                <hostName>xxx.xxx.xxx.xxx</hostName>
                <port>4003</port>
            </member>
        </members>       

b. Gateway Worker1:
        <members>
            <member>
                <hostName>xxx.xxx.xxx.xxx</hostName>
                <port>4500</port>
            </member>
            <member>
                <hostName>xxx.xxx.xxx.xxx</hostName>
                <port>4001</port>
            </member>
            <member>
                <hostName>xxx.xxx.xxx.xxx</hostName>
                <port>4003</port>
            </member>
        </members>
      <address uri="https://is.wso2.com:9453/oauth2/authorize"/>


c. Gateway Worker2:
        <members>
            <member>
                <hostName>xxx.xxx.xxx.xxx</hostName>
                <port>4500</port>
            </member>
            <member>
                <hostName>xxx.xxx.xxx.xxx</hostName>

                <port>4001</port>
   Update App URLs         </member>
            <member>
                <hostName>xxx.xxx.xxx.xxx</hostName>
                <port>4002</port>
            </member>
        </members>

        d. Store:
       <members>
            <member>
                <hostName>xxx.xxx.xxx.xxx</hostName>
                <port>8010</port>
            </member>
        </members>

        e. Publisher:
          <members>
            <member>
                <hostName>xxx.xxx.xxx.xxx</hostName>
                <port>8011</port>
            </member>
        </members>

etc/hosts

Open the etc/hosts at the machine root, with administrative privileges.  Add the following entries.  Please note, that this configuration would be different if you were installing these nodes on different physical or virtual machines, but here they are all on the same local machine.
127.0.0.1  elb.wso2.com mgt.am.wso2.com am.wso2.com gw.mgt.wso2.com store.wso2.com gw.worker1.wso2.com gw.worker2.wso2.com pub.wso2.com is.wso2.com userdb.mysql-wso2.com regdb.mysql-wso2.com apimgtdb.mysql-wso2.com am.cloud-test.wso2.com

SVN Deployment Synchronizer

It is necessary to configure subversion in order to sync deployment resources between the Gateway Manager and Workers.  This is a complex process, but there is no difference between this installation and other worker/manager clusters of WSO2 products.  Please follow the documentation here to set up the SVN Deployment Synchronizer.


Update App URLs

It is necessary to update the app urls in <gateway manager home>repository/deployment/server/synapse-configs/default/api/.  There are three urls here which need to be udpated: _TokenAPI_.xml, _RevokeAPI_.xml and _AuthorizeAPI_.xml.  These URLs should point to the key manager.

For the TokenAPI:
     <address uri="https://is.wso2.com:9453/oauth2/token"/>

For the RevokeAPI:
     <address uri="https://is.wso2.com:9453/oauth2/revoke"/>

For the AuthorizeAPI:
     <address uri="https://is.wso2.com:9453/oauth2/authorize"/>

Starting Up

Start up each server by navigating to the /bin directory and executing the wso2server.sh command. For the ELB, you will need to execute this command as an administrator.  For the Gateway Worker nodes, please execute the wso2server.sh with the -DWorkerNode=true command.
 sh wso2server.sh -DworkerNode=true

Testing

Start up a backend server of your choice (I usually use a standalone WSO2 DSS for testing).  Use the publisher node at https://store.wso2.com:9448/publisher to publish an API.  Use the Store at https://store.wso2.com:9447/store to subscribe to the API.  Use a stand-alone client of your choice (postman, curl, or SoapUI are my favorites) to perform the oAuth2 and test your API, using the load balanced gateway worker url: https:/am.wso2.com/.

Best of luck.  Please post your results, questions or any comments that could improve this post.  Cheers, y'all!