tl;dr: Don't know whether to use
resourceId
andresourceUri
when submitting usage events tohttps://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):
You can see the following relevant details:
- 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 - The 'Application Name' (2) in the 'Managed Application Details' is the customer-chosen name for the managed app object. Our example here is
myapp123
. - 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:
After the managed app has been fully deployed, the customer can see the managed app object in their own resource group:
Clicking into this managed app resource gives us the internal details:
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
.
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.