Integrate your SaaS solution with the Google Cloud Marketplace API using Producer Portal (Python)

1. Introduction

SaaS solutions on Google Cloud Marketplace are software solutions that run on your infrastructure, regardless of location, but are billed by Google.

In this codelab, you will set up a basic SaaS solution that integrates with Google Cloud Marketplace to:

  • Receive notifications when a user signs up for the sample solution.
  • Approve customers who want to sign up, and add them to your database.
  • Handle scenarios where customers want to change or cancel their billing plans.
  • Send usage reports to Google.

This codelab helps you to familiarize with the Google Cloud Marketplace procurement and service control APIs. Note this guide does not give a full product environment for testing.

2. Before you begin

  • Use Producer Portal to enable the codelab for your project, if you did not already enable it.

The direct link to Producer Portal is:

https://console.cloud.google.com/producer-portal?project=YOUR_PROJECT_ID

To enable the codelab, click Enable in the Codelabs panel on the right side of the screen.

  • Install Python 3 on your machine, with the following modules:
  • The Python Google Client APIs.
  • The google-cloud-pubsub client library.

To install the Python modules, use the following command:

pip install --upgrade google-api-python-client google-cloud-pubsub
  • Clone or download the GitHub repository for this codelab, using the following command:
git clone https://github.com/googlecodelabs/gcp-marketplace-integrated-saas.git
cd gcp-marketplace-integrated-saas

  • Set the GOOGLE_CLOUD_PROJECT environment variable to the ID of this project:
  • Linux:
export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
  • Windows:
set GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
  • Set the GOOGLE_APPLICATION_CREDENTIALS environment variable to the full path to the downloaded file:
  • Linux:
export GOOGLE_APPLICATION_CREDENTIALS="[YOUR_MACHINE]/path/service-account-key.json"
  • Windows:
set GOOGLE_APPLICATION_CREDENTIALS=[YOUR_MACHINE]/path/service-account-key.json
  • To view a sample solution in Google Cloud Marketplace, visit Producer Portal and click Codelab product in the Codelabs panel. You can also access the solution directly at https://console.cloud.google.com/marketplace/product/DEMO-YOUR_PROJECT_ID/isaas-codelab
  • In the Google Cloud Console, in your new project, enable the Partner Procurement API.

Next, set up the backend for the sample solution.

3. Integrating with Google Cloud Marketplace

At a high level, you integrate the sample solution with Google Cloud Marketplace in the following ways:

  • Integrate with Cloud Pub/Sub to receive notifications from Google Cloud Marketplace, such as when a user signs up for your solution. Your Partner Engineer creates a Cloud Pub/Sub topic that you must subscribe to for notifications.
  • Integrate with the Partner Procurement API to create accounts for new customers. You use the Partner Procurement API to update the accounts when users select, change, or cancel their subscription plans. To integrate with the API, you will need to build your own client library.
  • Integrate with Google Service Control to report usage information.

4. Subscribe to the Cloud Pub/Sub topic

When a user chooses a subscription plan, you get a notification from Google Cloud Marketplace through a Cloud Pub/Sub topic.

To listen to messages on a Cloud Pub/Sub topic, you must first create a subscription.

To create a subscription, use the create_subscription.py script:

cd gcp-marketplace-integrated-saas/tools
python create_subscription.py

To see the subscription, open the Cloud Pub/Sub dashboard in the Cloud Console:

https://console.cloud.google.com/cloudpubsub/subscriptions

Try a test subscription request

To test this sample app as a user, open the codelab product in Marketplace by visiting https://console.cloud.google.com/marketplace/product/DEMO-YOUR_PROJECT_ID/isaas-codelab. Make sure that you have your codelab project selected, and choose a subscription plan.

To see the Cloud Pub/Sub messages that are sent when you choose a plan, navigate to the root of the python3 directory in the repository, and run the following command:

~/gcp-marketplace-integrated-saas/python3$ python -m impl.step_1_pubsub.app

To see the sample code that listens for Cloud Pub/Sub messages, see https://github.com/googlecodelabs/gcp-marketplace-integrated-saas/blob/master/python3/impl/step_1_pubsub/app.py.

In the Cloud Pub/Sub message, the eventType field shows why the message was sent. When you choose a plan, you should see a message for eventType: ENTITLEMENT_CREATION_REQUESTED, which represents your earlier choice of subscription plan.

If you cancel your plan while this script is running, you'll see a new message, for eventType: ENTITLEMENT_CANCELLED.

Note that the above sample does not acknowledge messages. This allows you to more easily test by receiving the same messages each time you run your app.

To close the script, press CTRL + \.

5. Approve the account request

Now that you can receive messages from Google Cloud Marketplace, you must start handling the resources that the Google Cloud Marketplace Procurement service creates on behalf of the customer.

The first is the account resource. An account represents a customer's connection to your product. You must store the customer's Procurement account ID in your database, to map the relationship between their Google account and their account for your service.

When a customer chooses a plan, Google Cloud Marketplace sends a Cloud Pub/Sub notification that the customer is requesting an account. Your app must approve the request. In this codelab, you approve the account requests when the Cloud Pub/Sub messages are received.

Create the database for the account information

For this codelab, we use a simple JSON database that can keep track of customer accounts and purchases.

To test this sample, create a file with an empty JSON object, anywhere on your workstation:

{}

Set the PROCUREMENT_CODELAB_DATABASE environment variable to the full path to this file:

  • Linux:
export PROCUREMENT_CODELAB_DATABASE="YOUR_MACHINE/path/EMPTY_JSON_OBJECT.json"
  • Windows:
set PROCUREMENT_CODELAB_DATABASE=YOUR_MACHINE/path/EMPTY_JSON_OBJECT.json

The module that reads and writes the database is in python3/impl/database.

The sample implementation uses a schema that can be extended if you are integrating more than one product offering with Google Cloud Marketplace. The following is an example database entry for a user who subscribed to the Very Good plan in the sample app:

{
   "a2b3c4d5-b3f1-4dea-b134-generated_id":{
      "procurement_account_id":"generated-b3f1-4dea-b134-4a1d100c0335",
      "internal_account_id":"generated-45b7-4f4d-1bcd-2abb114f77de",
      "products":{
         "isaas-codelab":{
            "start_time":"2019-01-04T01:21:16.188Z",
            "plan_id":"very-good",
            "product_id":"isaas-codelab",
            "consumer_id":"project_number:123123345345"
         }
      }
   }
}

In your final implementation, you must connect your app with your own databases to link customers' Google Cloud Marketplace accounts with your own customer resources.

Approving the account

To approve the account request, run the following command:

~/gcp-marketplace-integrated-saas/python3$ python3 -m impl.step_2_account.app

The sample code to approve an account is in impl/step_2_account.

The sample implementation uses the Procurement class, which handles the interactions with the Procurement API. Here are its get_account() and approve_account() methods:

step_2_account/app.py

def _get_account_name(self, account_id):
    return 'providers/DEMO-{}/accounts/{}'.format(PROJECT_ID,
                                                  account_id)

def get_account(self, account_id):
    """Gets an account from the Procurement Service."""
    name = self._get_account_name(account_id)
    request = self.service.providers().accounts().get(name=name)
    try:
        response = request.execute()
        return response
    except HttpError as err:
        if err.resp.status == 404:
            return None

def approve_account(self, account_id):
    """Approves the account in the Procurement Service."""
    name = self._get_account_name(account_id)
    request = self.service.providers().accounts().approve(
        name=name, body={'approvalName': 'signup'})
    request.execute()

For this codelab, in the Procurement service, the provider ID is DEMO-YOUR_PROJECT_ID, where YOUR_PROJECT_ID is the project you created. While interacting with the Procurement API, the account name must use the following format:

providers/DEMO-YOUR_PROJECT_ID/accounts/account-id

Next, you approve an entitlement, which is a record of the customer's purchase.

6. Approve the entitlement

When a customer chooses a subscription plan in Google Cloud Marketplace, an account is created, and then a new entitlement request is created immediately. The entitlement represents the purchase of a service. Before the customer can start using the service, you must approve the entitlement request, and then set up the service for the customer to start using it.

When the sample app gets a Cloud Pub/Sub message with the eventType ENTITLEMENT_CREATION_REQUESTED, the entitlement is approved, and the app must wait for an ENTITLEMENT_ACTIVE message to record the entitlement in the database, then set up the resources for the customer.

To create the entitlement, run the following command:

~/gcp-marketplace-integrated-saas/python3$ python3 -m impl.step_3_entitlement_create.app

The code to approve the entitlement is in the sample implementation.

Next, you handle situations where a customer requests a change to their subscription plan.

7. Approve changes to an entitlement

If your service has multiple plans, you must handle requests from customers who might want to upgrade or downgrade their existing plan.

If your service has only one plan, skip to Handle canceled purchases.

There's no technical difference between an entitlement becoming active for the first time and becoming active after a plan change. For this reason, the sample implementation has a shared handleActiveEntitlement() method for both cases. This method checks incoming messages for entitlement-related events:

step_4_entitlement_change/app.py

def handleActiveEntitlement(self, entitlement, customer, accountId):
  """Updates the database to match the active entitlement."""

  product = {
      'product_id': entitlement['product'],
      'plan_id': entitlement['plan'],
  }

  if 'consumerId' in entitlement:
    product['consumer_id'] = entitlement['consumerId']

  customer['products'][entitlement['product']] = product

  self.db.write(accountId, customer)

The following snippet checks whether the eventType is ENTITLEMENT_PLAN_CHANGE_REQUESTED or ENTITLEMENT_PLAN_CHANGED:

step_4_entitlement_change/app.py

elif eventType == 'ENTITLEMENT_PLAN_CHANGE_REQUESTED':
  if state == 'ENTITLEMENT_PENDING_PLAN_CHANGE_APPROVAL':
    # Don't write anything to our database until the entitlement becomes
    # active within the Procurement Service.
    self.approveEntitlementPlanChange(id, entitlement['newPendingPlan'])
    return True

elif eventType == 'ENTITLEMENT_PLAN_CHANGED':
  if state == 'ENTITLEMENT_ACTIVE':
    # Handle an active entitlement after a plan change.
    self.handleActiveEntitlement(entitlement, customer, accountId)
    return True

In your final implementation, when the entitlement transitions back to the ENTITLEMENT_ACTIVE state, your listener method should update your database to reflect the change and do any necessary provisioning.

Depending on how you set up your product with your Partner Engineer, your service might not allow downgrades or cancellations until the end of a billing cycle. In such cases, the plan change will continue to be pending even after the approval, but the entitlement will not go back to the ENTITLEMENT_ACTIVE state until the plan change completes.

For the code that checks for and approves entitlement changes, see the sample implementation.

Next, you handle situations where customers cancel their purchases.

8. Handle canceled purchases

Customers may choose to cancel their purchases. Depending on how you set up the product with your Partner Engineer, the cancellation can take effect immediately, or at the end of the billing cycle.

When a customer cancels their purchase, a message with the eventType ENTITLEMENT_PENDING_CANCELLATION is sent. If you've set up your product to process cancellations immediately, a message with the eventType ENTITLEMENT_CANCELLED is sent soon after.

step_5_entitlement_cancel/app.py

elif eventType == 'ENTITLEMENT_CANCELLED':
  # Clear out our records of the customer's plan.
  if entitlement['product'] in customer['products']:
    del customer['products'][entitlement['product']]

  ### TODO: Turn off customer's service. ###
  self.db.write(accountId, customer)
  return True

elif eventType == 'ENTITLEMENT_PENDING_CANCELLATION':
  # Do nothing. We want to cancel once it's truly canceled. For now it's
  # just set to not renew at the end of the billing cycle.
  return True

elif eventType == 'ENTITLEMENT_CANCELLATION_REVERTED':
  # Do nothing. The service was already active, but now it's set to renew
  # automatically at the end of the billing cycle.
  return True

Your service must wait for the ENTITLEMENT_CANCELLED message to then remove the entitlement from your database, and turn off the service for the customer.

After the entitlement is canceled, it is deleted from Google's systems, and a message with the eventType ENTITLEMENT_DELETED is sent:

step_5_entitlement_cancel/app.py

elif eventType == 'ENTITLEMENT_DELETED':
  # Do nothing. Entitlements can only be deleted when they are already
  # cancelled, so our state is already up-to-date.
  return True

For the code that cancels entitlement, see the sample implementation.

9. Sending usage reports

Some services have usage-based components, where Google needs to know about customers' usage of the service to charge the customer the correct amount. Your service must report usage through the Google Service Control API.

If your service does not have usage-based components, skip this section.

For detailed information on sending usage reports, see the onboarding documentation.

Usage reports should be sent to the Google Service Control API hourly. In this codelab, the reports are sent using a script that you could schedule as a cron job. The script stores the time of the last usage report in the database, and uses that as the start time to measure usage.

The script checks each active customer of the service, and sends a usage report to Google Service Control using the customer entitlement's consumer_id field. The script then updates the database entry for the customer to have a last_report_time set to the end time of the usage report just sent.

Google Service Control exposes two methods: check and report. The former should always be called immediately before a call to the latter. If the former has any errors, the customer's service should be disabled until it is fixed.

All usage for a given entitlement is attributed to a single usageReportingId. However, for SaaS products, this usage is associated with the [Charges not specific to a project] line item in Google Cloud Billing. If your SaaS product might be shared broadly within a customer's organization, and you want to support cost attribution, we recommend that all of your services include the optional userLabels field on their usage report operation.

Google Cloud Marketplace reserves the cloudmarketplace.googleapis.com/resource_name and cloudmarketplace.googleapis.com/container_name label keys. These labels are intended to capture the context of the usage within your native service and resource hierarchy. The customer-assigned names of these resources would be included as label values in usage reports. We recommend that you include these labels in your usage reports by default.

Label Key

Label Value

Description

cloudmarketplace.googleapis.com/resource_name

RESOURCE_NAME

The name of the resource associated with a usage metric.

cloudmarketplace.googleapis.com/container_name

CONTAINER_NAME

The name of a resource container.

The following snippet reports the usage for the demo app and attributes the usage for the SaaS product to the customer-assigned resource named products_db. For this codelab, the service_name is isaas-codelab.mp-marketplace-partner-demos.appspot.com.

operation = {
  'operationId': '<UUID>',
  'operationName': 'Codelab Usage Report',
  'consumerId': 'project_number:<Project Number>',
  'startTime': '<Timestamp>',
  'endTime': '<Timestamp>',
  'metricValues': [{
      'int64Value': 100,
  }],
  'userLabels': {
    'cloudmarketplace.googleapis.com/container_name': 'saas-storage-solutions',
    'cloudmarketplace.googleapis.com/resource_name': 'products_db'
  }
}

service.services().report(
    serviceName=service_name, body={
        'operations': [operation]
    }).execute()
product['last_report_time'] = end_time
database.write(customer_id, customer)

See the sample implementation of this script for the full code. To make a sample usage report, run the following command:

~/gcp-marketplace-integrated-saas/python3$ python3 -m impl.step_6_usage_reporting.report isaas-codelab.mp-marketplace-partner-demos.appspot.com

10. Congratulations!

You learned how your SaaS solution can integrate with Google Cloud Marketplace to handle customer accounts and entitlements, and to report usage against a service. For full integration steps, refer to the backend integration documentation.

Clean up

If you no longer plan to use them, delete the following resources:

  • The Cloud Pub/Sub subscription
  • The service account and its keys
  • Optionally, the project that you created
  • Optionally, the billing account that you created

What's next

Integrate your frontend

The samples in this codelab automatically approve accounts and entitlements. In practice, your customers must be directed to a sign-up page that you create, where they can create accounts in your system. After they sign up successfully, you must make the API requests to approve their accounts and entitlements.

For information on integrating your app's frontend, see the Google Cloud Marketplace documentation.

Learn more about offering SaaS solutions

For an overview of offering SaaS solutions on Google Cloud Marketplace, see Offering SaaS solutions.