Azure Marketplace Metered Billing- Picking the correct ID when submitting usage events

Published Jun 23 2022 02:43 AM 905 Views
Microsoft

tl;dr: Don't know whether to use resourceId and resourceUri when submitting usage events to https://marketplaceapi.microsoft.com/api/usageEvent or .../batchUsageEvent? Read on...

In Azure Marketplace, both SaaS offers, and Azure Managed Applications allow the publisher to submit custom usage events to the Metering service API (if you have enabled custom metering dimensions on your marketplace offer). The metering service API accepts either a single usage event (on the usageEvent endpoint), or a batch (on the batchUsageEvent endpoint). A single usage event JSON message looks like this:

 

{
  "effectiveStartTime": "2018-12-01T08:00:00", 
  "planId": "id-of-the-plan-as-configured-in-partner-center"
  "dimension": "id-of-the-billing-dimension-as-configured-in-partner-center", 
  "quantity": 5.0, 
  "resourceId": "deadbeef-123...guid" ... or ... "resourceUri": "/subscription/..."
}

 

In this message, the first 4 lines are kind-of-easy: effectiveStartTime is the hour (in UTC) for which the usage has been aggregated. planId and dimension correspond are the identifiers configured in Partner Center, quantity is the JSON serialization of the quantity of usage that happened for the given dimension in the given hour, either something like 5 or 5.0 (or 0.02 or 23892).

 

But what about that resource... stuff, do I have to use a "resourceId", or "resourceUri", or even both?!? And where does that value come from? Well, that depends on the offer type you have..

 

Software-as-a-Service (SaaS) offers - use resourceId

For a SaaS offer, it is comparably easy: When a customer purchases a SaaS offer, they get sent to your 'landing page', from which you call the 'SaaS fulfillment Subscription APIs v2', specifically the api/saas/subscriptions/resolve endpoint, which returns (amongst other things) the 'purchased SaaS subscription ID' back to your solution:

 

{
  "id": "<guid>", // purchased SaaS subscription ID, which will be used as resourceId in a usage event
  "subscriptionName": "Contoso Cloud Solution", // SaaS subscription name
  "offerId": "offer1", // purchased offer ID
  "planId": "silver", // purchased offer's plan ID
  ...
}

You should use the id from that response, i.e. the purchased SaaS subscription ID, as resourceId value in your metering API usage event submissions. This value is a GUID, which your SaaS solution can use to track and aggregate the usage of your customer.

 

The SaaS offer also has a resourceUri. When purchasing a SaaS offer, the purchaser puts the offer into one of their resource groups (customer-rg1 below), and gives it a name (SomeNameTheCustomerChose below). The ARM resource ID of that would be the resourceUri, and looks like this:

 

/subscriptions/.../resourceGroups/customer-rg1/providers/Microsoft.SaaS/resources/SomeNameTheCustomerChose

Having said that, you certainly can ignore the resourceUri with SaaS offer, as you already have the resourceId, which is sufficient.

Azure Managed Applications - use resourceUri

An Azure Application Offer can either be a 'solution template' or a 'managed application'. Only the 'managed app' (Azure Managed Application) can submit usage to the Metering service API. A managed app is a bunch of resources described in an ARM template, which are deployed into a 'managed resource group' in the customer's Azure subscription. The ISV/publisher has administrative control over these resources to 'manage' them, therefore the name 'managed app'; the customer themselves usually cannot change the resource in the managed resource group, even though they live in the customer's subscription.

When the customer purchases the managed app via the Marketplace in the Azure Portal, they select a resource group where the 'managed app' object itself lives, and that object is something of type Microsoft.Solutions/applications. The resource group which contains the managed app resource object is owned by the customer. This managed app resource 'points' to the managed resource group, and vice versa.

Consider the following purchase screen (omitting the app-specific details):

 

2022-06-23-managed-app-purchase-1.png

 

 

You can see the following relevant details:

  1. The 'Resource group' (1) at the top, named customer-owned-rg, is the resource group in which the managed app object will be deployed. The customer has full control over it. In
  2. The 'Application Name' (2) in the 'Managed Application Details' is the customer-chosen name for the managed app object. Our example here is myapp123.
  3. The 'Managed Resource Group' (3) at the bottom is the RG where all resources from the ISV's ARM-template will be deployed to. In our example, this is managed-rg-for-myapp123.
    • The customer has (usually) no control over this.
    • The ISV/publisher can manage the resources in it.
    • The 'Managed Resource Group' field initially has a value of 'MTG-somethingsomething', but the customer can choose the managed resource group's name, to better align with own naming conventions.

The purchase confirmation summarizes the data again:

 

2022-06-23-managed-app-purchase-2.png

After the managed app has been fully deployed, the customer can see the managed app object in their own resource group:

 

2022-06-23-managed-app-purchase-3.png

Clicking into this managed app resource gives us the internal details:

 

2022-06-23-managed-app-purchase-4.png

To summarize our progress up until now, in our sample, we have the following names:

 

Data Value
Resource group customer-owned-rg
Application Name myapp123
Managed Resource Group managed-rg-for-myapp123

 

The resource ID of the managed app in the ARM control plane is this:

 

/subscriptions/.../resourceGroups/customer-owned-rg/providers/Microsoft.Solutions/applications/myapp123

The resource ID of the managed resource group is this: 

 

/subscriptions/.../resourceGroups/managed-rg-for-myapp123

 

When you now fetch ARM properties of the managed resource group on the Azure management API, you get the following response:

 

GET /subscriptions/.../resourceGroups/managed-rg-for-myapp123?api-version=2019-07-01

{
  "id": "/subscriptions/.../resourceGroups/managed-rg-for-myapp123",
  "type": "Microsoft.Resources/resourceGroups",
  "name": "managed-rg-for-myapp123",
  "location": "westeurope",
  "managedBy": "/subscriptions/.../resourceGroups/customer-owned-rg/providers/Microsoft.Solutions/applications/myapp123",
  "properties": { "provisioningState": "Succeeded" }
}

 

You can see that the description of the managed resource group has a managedBy property, which points to the resource ID of the managed app.

 

The resourceUri for a managed app

And now we have which resourceUri we could use when submitting usage records to the metering API: The resourceUri is managedBy property in the the ARM resource representation of the managed resource group:

 

{
  "effectiveStartTime": "2018-12-01T08:00:00", 
  "planId": "plan1"
  "dimension": "id-of-the-billing-dimension-as-configured-in-partner-center", 
  "quantity": 5.0, 
  "resourceUri": "/subscriptions/.../resourceGroups/customer-owned-rg/providers/Microsoft.Solutions/applications/myapp123"
}

The good thing with the resourceUri is that it's immediately available, even during the actual ARM deployment...

The resourceId for a managed app

When we submit the usage to the metering API, we get back a response like this:

 

{
  "status": "Accepted",
  "usageEventId": "...",
  "messageTime": "2018-12-01T10:00:25.7798568Z",

  "effectiveStartTime": "2018-12-01T08:00:00", 
  "planId": "plan1"
  "dimension": "id-of-the-billing-dimension-as-configured-in-partner-center", 
  "quantity": 5.0, 
  "resourceUri": "/subscriptions/.../resourceGroups/customer-owned-rg/providers/Microsoft.Solutions/applications/myapp123", 
"resourceId": "accd441b-01e8-48c7-8c73-90a0ea5cec0a"
}

It echoes our submitted request, and augments it with further details, such as messageTime (when the request arrived), the "status": "Accepted", or a unique usageEventId (under which Azure tracks that submission internally).

But - there is also a resourceId. Where did that came from, and could we have retrieved that ourself?

 

The resourceId is actually an ARM property on the managed app. Once Azure has fully (and successfully) provisioned all the resources in the managed resource group, Azure sets a billingDetails property on the managed app, which contains a resourceUsageId property, which is our resourceId for usage events:

 

GET /subscriptions/.../resourceGroups/customer-owned-rg/providers/Microsoft.Solutions/applications/myapp123

{
    "id": "/subscriptions/.../resourceGroups/customer-owned-rg/providers/Microsoft.Solutions/applications/myapp123",
    "name": "myapp123",
    "type": "Microsoft.Solutions/applications",
    "kind": "MarketPlace",
    "location": "westeurope"
    "properties": {
        "billingDetails": {
            "resourceUsageId": "accd441b-01e8-48c7-8c73-90a0ea5cec0a"
        },
        "managedResourceGroupId": "/subscriptions/.../resourceGroups/managed-rg-for-myapp123",
        "provisioningState": "Succeeded",
        "managementMode": "Managed",
        "customerSupport": {...},
        "supportUrls": {...}
    },
    "plan": {
        "product": "metered-billing-test-01-preview",
        "name": "plan1",
        "publisher": "...",
        "version": "1.0.0"
    }
}

 

So after the (successful) deployment, if you are authorized to read the managed app, which is not in the managed resource group, but in the customer's resource group, then you can get the .properties.billingDetails.resourceUsageId.

 

It is important to note that the billingDetails are only set by Azure, once the deployment successfully went through. You will not have access to the billingDetails during the ARM template provisioning.

 

So in a usage event, you can submit 

 

{
"effectiveStartTime": "...", "planId": "...", "dimension": "..", "quantity": ...,
"resourceId": "accd441b-01e8-48c7-8c73-90a0ea5cec0a"
}

or

 

{
"effectiveStartTime": "...", "planId": "...", "dimension": "..", "quantity": ...,
"resourceUri": "/subscriptions/.../resourceGroups/customer-owned-rg/providers/Microsoft.Solutions/applications/myapp123"
}

or even both

 

{
"effectiveStartTime": "...", "planId": "...", "dimension": "..", "quantity": ...,
"resourceUri": "/subscriptions/.../resourceGroups/customer-owned-rg/providers/Microsoft.Solutions/applications/myapp123",
"resourceId": "accd441b-01e8-48c7-8c73-90a0ea5cec0a"
}

 

However, when submitting both, these must obviously belong together... Having said that, even if it is possible to submit both values, it's unnecessary work, so you certainly want to focus on the resourceUri .

 

Just do what's simplest for you to retrieve. Keep it simple. 

Summary

For a SaaS offer, you should be using the SaaS subscription ID (a GUID) as resourceId.

 

For a managed app, you should use the managed resource group's managedBy property (something like /subscriptions/.../resourceGroups/.../providers/Microsoft.Solutions/applications/...) as resourceUri.

 

 

Spoiler
Last, but not least, a shameless plug for a project which might simplify your life: If you are building a solution with metered billing, you might want to have a look at our 'metered billing accelerator' code base here: microsoft/metered-billing-accelerator (github.com). This is an event-sourced solution to do the aggregation and submission for you. Feel free to reach out on github (chgeuer), twitter (chgeuer) or email (chgeuer) to me. 
Co-Authors
Version history
Last update:
‎Jun 23 2022 07:26 AM
Updated by: