Introduction

Scenario

In this tutorial, we will look at how one could leverage the multi-tenancy feature from Kill Bill. The scenario we will describe, is one where we would like to provide 'Subscription As A Service' (SaaS) in the cloud (AWS). The tutorial will skip all the operational good practices such as redundancy of instances, firewalls, load balancers and focus instead on the Kill Bill specifics. Also, note that when deploying in the cloud where instances can come and go, the configuration of the bus and notifications queues need to be understood.

We will assume that we only need one plugin to make the payments, and as an example we will use the Stripe plugin.

Users and Roles

Let’s start by defining some users and roles:

  • The user setting up the Subscription As A Service in the cloud, is called superadmin, and will have all the privileges to configure the tenants and setup the roles associated with the other users.

  • The admin user associated to each tenant will be called admin_X where X is the name of the tenant.

Let’s take the case of a small yoga studio that wants to use our Subscription As A Service to provide subscriptions to its users: The name of the merchant is Shakti, and so we will create a user called admin_shakti for the tenant shakti. The admin_shakti user will have full privileges for its Shakti tenant and will not see any of the other tenants.

Of course, for our own purpose of providing the service, we will also create a very first tenant to invoice our merchants (e.g the Shakti business). That tenant will be called saas ('Subscription As A Service'), and the associated admin user will be called admin_saas.

Also, for sake of simplicity, we will setup Kill Bill to use a static Shiro configuration file to configure the various admin users/roles. In real life, it would be more practical to rely on LDAP rather than static configuration, or use the Database configuration approach to avoid having to restart Kill Bill when creating new users/roles. The LDAP configuration is supported by Shiro but we will not describe the steps in that tutorial to keep it simple.

Kill Bill Multi-tenant Deployment

Overview of AWS Deployment Steps

In order to install Kill Bill on AWS you can use our docker images and rely on the aws from docker-machine. Alternatively you can setup things by hand, for instance by following the following steps.

The important piece is that=, the port 8080 should be publicly available for Kill Bill (in that simple setup with no firewall) and the port 3000 should be publicly available for KAUI.

Notes:

  • We are using the version VERSION for killbill and kaui below to avoid having to update the documentation each time we release. We do suggest you use the latest version as shown in the download page.

  • The deployment will consist in separate instances:

    • One instance for killbill, where we will deploy the docker image killbill/killbill:latest

    • One instance to configure the admin UI (KAUI) where we will deploy the deocker image killbill/kaui:latest

    • Finally RDS instance

For the Kill Bill Deployment, the killbill database schema will need to include:

So, for each of those schema, you could install the schema from the EC2 zone with the following command:

killbill_ec2> curl -q  http://...ddl | mysql -h RDS_URL -u RDS_USER -pRDS_PWD killbill

For the KAUI deployment, the kaui database will need to include:

Configuration Files

Since we want to realy on the static shiro.ini file, we need to make sure such a file exists and is available. Fortunately, there is config variable, KILLBILL_SHIRO_RESOURCE_PATH, that we can pass to our docker image, to take of that.

The shiro.ini file will look like the following:

shiro.ini
[users]
superadmin = PWD, root
admin_shakti = PWD, root_tenant
admin_saas = PWD, root_tenant
[roles]
root = *:*
root_tenant = account,catalog,custom_field,entitlement,invoice,overdue,payment,payment_method,tag,usage

Starting the Kill Bill Instance

Again, here we will be reusing the same instances we configured in the http://killbill.io/tutorials/aws/ [aws tutorial], and start first pull and and then run the docker containers in these EC2 instances:

Kill Bill Docker Sequence:
killbill_ec2> sudo docker pull killbill/killbill:VERSION
killbill_ec2> docker run -tid \
--name killbillsaas \
-p 8080:8080 \
-e KILLBILL_PLUGIN_STRIPE=1 \
-e KILLBILL_PLUGIN_ANALYTICS=1 \
-e KILLBILL_SHIRO_RESOURCE_PATH=/home/ubuntu/killbill/etc/shiro.ini \
-e KILLBILL_CONFIG_DAO_URL=jdbc:mysql://RDS_URL/killbill \
-e KILLBILL_CONFIG_DAO_USER=RDS_USER \
-e KILLBILL_CONFIG_DAO_PASSWORD=RDS_PWD \
-e KILLBILL_CONFIG_OSGI_DAO_URL=jdbc:mysql://RDS_URL/killbill \
-e KILLBILL_CONFIG_OSGI_DAO_USER=RDS_USER \
-e KILLBILL_CONFIG_OSGI_DAO_PASSWORD=RDS_PWD \
killbill/killbill:VERSION
killbill_ec2> sudo docker logs -f killbillsaas  // check for instance to be up and running

Finally, since we run the analytics plugin, we need to configure the analytics tables. There is a script that can be run to configure the analytics tables with all the existing views and reports. The script needs to be run from the analytics repo, and it will both hit some endpoints on the running instance of killbill and also create some views through mysql client. If your RDS instance is not visible to the public world, you have two options

  1. Clone the repo on the killbill ec2 zone and run the script from there (but that might require installing git, …​)

  2. Clone the repo on your local machine and create a tunnel (this is the option we will highlight below):

# Create Tunnel through our publicly visible EC2 instance to be able to access the RDS instance
laptop> ssh -i ~/<yourkey>.pem ubuntu@KILLBILL_IP  -L13306:RDS_URL:3306 -N

laptop> git clone https://github.com/killbill/killbill-analytics-plugin.git
laptop> cd src/main/resources
laptop> export KILLBILL_HOST=KILLBILL_PUBLIC_IP; export KILLBILL_USER=superadmin; export KILLBILL_PASSWORD=PWD; export MYSQL_HOST=RDS_IP; export MYSQL_HOST=RDS_PORT; export MYSQL_PASSWORD=RDS_PWD ; export MYSQL_USER=RDS_USER; /bin/bash ./seed_reports.sh

If you look in your RDS instance you should see reports configured in the analytics_reports table and all the views v_report_* such as v_report_accounts_summary should exist.

Starting the KAUI Instance

Kaui Docker Sequence:
kaui_ec2> sudo docker pull killbill/kaui:VERSION
kaui_ec2> docker run -tid \
--name kaui-saas \
-p 3000:8080 \
-e KAUI_API_KEY= \
-e KAUI_API_SECRET= \
-e KAUI_CONFIG_DAO_URL=jdbc:mysql://RDS_URL/kaui \
-e KAUI_CONFIG_DAO_USER=RDS_USER \
-e KAUI_CONFIG_DAO_PASSWORD=RDS_PWD \
-e KAUI_URL=http://KILLBILL_IP:8080 \
killbill/kaui:VERSION
kaui_ec2> sudo docker logs -f kaui-saas

Saas Setup

Creating the tenants and configuring allowed users

KAUI has been enhanced with new admin screens, that are described in the Multi-tenancy screens section of that documentation.

The first step is to login as superadmin to have the rights to create new tenants and configure all allowed users.

Starting on the /admin_tenants screen, click to Configure a New Tenant to create the 2 tenants saas and shakti; for e.g for shakti we would enter:

  • Name : shakti

  • API Key: some_key_fort_shakti

  • API Secret: some_secret_for_shakti

  • Click on the Create tenant to also create the tenant in Kill Bill.

At this point, the tenant exists in Kill Bill and is known from KAUI as well.

We can then configure the allowed users. KAUI needs to know who can access which tenant, and this information is kept in the KAUI database. It really means that any user known from Kill Bill (shiro.ini) will be able to make API calls against any tenant provided the user specifies the correct tenant api_key and api_secret, so the security resides behind keeping those keys secret. On the screen /admin_allowed_users, click on Add a new Allowed User; for e.g for the shakti administrator we would enter:

  • Name : admin_shakti # This has to match the shiro.ini configuration

  • Description : Admin user for tenant shakti

Then you will be prompted to select the tenant this users has access to. In our example of admin_shakti, we will select the available tenant shakti from the list that we previously configured.

Obviously for the user superadmin we would add the two tenants saas and shakti.

When all the users and tenants have been configured, you can try to logout, and login as a specific user (for e.g admin_shakti). If the user has only access to one tenant, the process of login-in will directly assign that tenant and all subsequent operations will be made against that tenant. If the user has more than one tenant, the user will be prompted to chose which tenant to use right after the login screen.

Configuring each tenant

Both Kill Bill and KAUI have been improved to now support uploading per tenant configuration:

  • The UI offers new screens to upload all these new configs

  • The plugins get notified when such config occurs so they can take action if needed

  • In multi-node scenario, there is a mechanism to make sure all nodes will be notified and refresh their view

The following per-tenant configuration can now be uploaded:

  • Per Tenant Versioned Catalog: Each new upload will create a new version of the catalog

  • Per Tenant Overdue Config: Each new upload will overwrite the previous version of the overdue.xml associated with this tenant

  • Per Tenant Invoice Template: Each new upload will overwrite the previous version of the invoice template associated with this tenant

  • Per Tenant Invoice Translation: Each new upload will overwrite the previous version of the invoice translation associated with this tenant

  • Per Tenant Catalog Translation: Each new upload will overwrite the previous version of the catalog translation associated with this tenant

  • Per Tenant Plugin Translation: Each new upload will overwrite the previous version of the config associated with this tenant and this specific plugin

Let’s do some basic configuration for the tenant 'shakti'. We will upload a catalog and then a specific configuration for the stripe plugin. You can login as superadmin or admin_shakti since both these users have the right to access that tenant. From the screen /admin_tenants/ chose the shakti tenant.

Then, let’s start with the stripe plugin: Create a valid config and then use the Plugin Config section of the page to specify the plugin name killbill-stripe and then upload the yml shown below:

Per tenant stripe.yml:
:stripe:
  :api_secret_key: 'YOUR_VALID_TENANT_API_SECRET_KEY'
  :api_publishable_key: 'YOUR_VALID_TENANT_API_PUBLISHABLE_KEY'

Then let’s now upload a catalog for our tenant: Create the following catalog and then use the Tenant Catalog XML section to upload the file associated with the tenant.

Shakti Catalog:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">

    <effectiveDate>2013-02-08T00:00:00+00:00</effectiveDate>
    <catalogName>Shakti</catalogName>

    <recurringBillingMode>IN_ADVANCE</recurringBillingMode>

    <currencies>
        <currency>USD</currency>
        <currency>EUR</currency>
    </currencies>

    <products>
        <product name="Ashtanga">
            <category>BASE</category>
            <included>
                <addonProduct>Pranayama</addonProduct>
            </included>
        </product>
        <product name="Flow">
            <category>BASE</category>
        </product>
        <product name="Iyengar">
            <category>BASE</category>
            <available>
                <addonProduct>Pranayama</addonProduct>
            </available>
        </product>
        <product name="Pranayama">
            <category>ADD_ON</category>
        </product>
    </products>

    <rules>
        <changePolicy>
            <changePolicyCase>
                <policy>IMMEDIATE</policy>
            </changePolicyCase>
        </changePolicy>
        <changeAlignment>
            <changeAlignmentCase>
                <alignment>START_OF_BUNDLE</alignment>
            </changeAlignmentCase>
        </changeAlignment>
        <cancelPolicy>
            <cancelPolicyCase>
                <policy>IMMEDIATE</policy>
            </cancelPolicyCase>
        </cancelPolicy>
        <createAlignment>
            <createAlignmentCase>
                <alignment>START_OF_BUNDLE</alignment>
            </createAlignmentCase>
        </createAlignment>
        <billingAlignment>
            <billingAlignmentCase>
                <alignment>ACCOUNT</alignment>
            </billingAlignmentCase>
        </billingAlignment>
        <priceList>
            <priceListCase>
                <toPriceList>DEFAULT</toPriceList>
            </priceListCase>
        </priceList>
    </rules>

    <plans>
        <plan name="ashtanga-monthly">
            <product>Ashtanga</product>
            <initialPhases>
                <phase type="TRIAL">
                    <duration>
                        <unit>DAYS</unit>
                        <number>30</number>
                    </duration>
                    <fixed>
                        <fixedPrice> <!-- empty price implies $0 -->
                        </fixedPrice>

                    </fixed>
                </phase>
            </initialPhases>
            <finalPhase type="EVERGREEN">
                <duration>
                    <unit>UNLIMITED</unit>
                </duration>
                <recurring>
                    <billingPeriod>MONTHLY</billingPeriod>
                    <recurringPrice>
                        <price>
                            <currency>EUR</currency>
                            <value>150.00</value>
                        </price>
                        <price>
                            <currency>USD</currency>
                            <value>175.00</value>
                        </price>
                    </recurringPrice>
                </recurring>
            </finalPhase>
        </plan>
        <plan name="flow-monthly">
            <product>Flow</product>
            <initialPhases>
                <phase type="TRIAL">
                    <duration>
                        <unit>DAYS</unit>
                        <number>30</number>
                    </duration>
                    <fixed>
                        <fixedPrice> <!-- empty price implies $0 -->
                        </fixedPrice>
                    </fixed>
                </phase>
            </initialPhases>
            <finalPhase type="EVERGREEN">
                <duration>
                    <unit>UNLIMITED</unit>
                </duration>
                <recurring>
                    <billingPeriod>MONTHLY</billingPeriod>
                    <recurringPrice>
                        <price>
                            <currency>EUR</currency>
                            <value>100.00</value>
                        </price>
                        <price>
                            <currency>USD</currency>
                            <value>125.00</value>
                        </price>
                    </recurringPrice>
                </recurring>
            </finalPhase>
        </plan>
        <plan name="iyengar-monthly">
            <product>Iyengar</product>
            <initialPhases>
                <phase type="TRIAL">
                    <duration>
                        <unit>DAYS</unit>
                        <number>30</number>
                    </duration>
                    <fixed>
                        <fixedPrice> <!-- empty price implies $0 -->
                        </fixedPrice>

                    </fixed>
                </phase>
            </initialPhases>
            <finalPhase type="EVERGREEN">
                <duration>
                    <unit>UNLIMITED</unit>
                </duration>
                <recurring>
                    <billingPeriod>MONTHLY</billingPeriod>
                    <recurringPrice>
                        <price>
                            <currency>EUR</currency>
                            <value>115.00</value>
                        </price>
                        <price>
                            <currency>USD</currency>
                            <value>150.00</value>
                        </price>
                    </recurringPrice>
                </recurring>
            </finalPhase>
        </plan>
        <plan name="pranayama-monthly">
            <product>Pranayama</product>
            <initialPhases>
                <phase type="TRIAL">
                    <duration>
                        <unit>DAYS</unit>
                        <number>30</number>
                    </duration>
                    <fixed>
                        <fixedPrice> <!-- empty price implies $0 -->
                        </fixedPrice>

                    </fixed>
                </phase>
            </initialPhases>
            <finalPhase type="EVERGREEN">
                <duration>
                    <unit>UNLIMITED</unit>
                </duration>
                <recurring>
                    <billingPeriod>MONTHLY</billingPeriod>
                    <recurringPrice>
                        <price>
                            <currency>EUR</currency>
                            <value>25.00</value>
                        </price>
                        <price>
                            <currency>USD</currency>
                            <value>35.00</value>
                        </price>
                    </recurringPrice>
                </recurring>
            </finalPhase>
        </plan>
    </plans>
    <priceLists>
        <defaultPriceList name="DEFAULT">
            <plans>
                <plan>ashtanga-monthly</plan>
                <plan>flow-monthly</plan>
                <plan>iyengar-monthly</plan>
				<plan>pranayama-monthly</plan>
            </plans>
        </defaultPriceList>
    </priceLists>
</catalog>

You should now do the same kind of configuration for the other saas tenant and you are ready to start creating account, subscriptions, invoices and make payments on both tenants in parallel!