Pax8 leverages Acronis Cyber Platform to develop customized commitment models in its cloud marketplace

Pax8 is the leader in cloud distribution for the IT channel. Born out of the challenges emerging within the traditional distribution ecosystem and the growth of cloud delivery and consumption models, Pax8’s mission is to enable the IT channel to simplify cloud buying through billing, provisioning, automation, PSA integrations, and pre-and-post sales support. The company is an award-winning disruptor in the market, earning accolades like NexGen’s Best in Show two years in a row, Biggest Buzz at IT Nation, CRN’s Coolest Cloud Vendor, Best in Show at two consecutive XChange conferences.

Pax8 leverages Acronis Cyber Platform to develop customized commitment models in its cloud marketplace

Pax8 recognized early on that it needed to make the right investments in technology and integrations to provide its MSP (Managed Service Provider) partners as well as third-party solutions vendors an optimal experience. This ultimately involved automation and APIs – the cornerstone to interacting with the Pax8 cloud marketplace. According to Craig Donnovan, Pax8’s VP of Partner Solutions, “Integrations are key to the MSP space. This is because an MSP is constantly managing a complex ecosystem of billing and quoting tools, inventory and ticket tracking, ordering products and provisioning. Often these systems are in different locations which adds to the complexity.”

This is where the Acronis Cyber Platform came into the picture. Pax8’s MSP partners had been asking about Acronis and were particularly drawn to its flexible architecture that housed multiple products under a single, multi-tenant management platform that let MSP partners provide cyber protection solutions and backup to the Acronis cloud or their own cloud.

In order to get the suite of Acronis Cyber Protection products into the Pax8 marketplace, the two companies worked together on the integration. Pax8 was one of the first companies to take advantage of the Acronis Cyber Platform, a set of open APIs and SDKs designed to enable third parties to easily integrate with Acronis solutions. The technical integration took about 2 months in total. Because the Acronis APIs were easy to use and well documented and the Acronis development team was very responsive and collaborative, Pax8 was able to do some custom development which was a first of a kind for the company. Specifically, Pax8 was able to implement both commitment and non-commitment models on a single vendor. Essentially, a partner is therefore able to commit to a tier upfront and get a discount based on the aggregate number of licenses are being deployed.

Watch the video case study for more details.

Let’s drill down one of the tasks you may perform as a part of MSP automation tasks.

Enabling the backup service for a user in a customer tenant

Let’s assume that the partner tenant has the following:
• The backup service is enabled.
• The Workstations and Backup storage offering items are enabled.
• An administrator account is activated. This account will be used to perform the operations in the tenant via the API.
• A customer and a user are absent

To enable the backup services for a user in a customer tenant we need to perform the following tasks:

  1. Create a customer tenant in your partner tenant:
    POST /tenants
  2. Enable the Workstations and Backup storage offering items for the customer tenant:
    GET /tenants/{partner_id}/offering_items/available_for_child
    PUT /tenants/{customer_id}/offering_items
    This automatically enables the backup service for the tenant.
  3. Create a user account in the customer tenant:
    GET /users/check_login POST /users
  4. Assign the Backup User role to this account:
    PUT /users/{user_id}/access_policies
    This automatically enables the backup service with the Workstations and Backup storage offering items for the account and set the same quotas for the account as the ones set for the tenant.
  5. Activate the account by setting its password:
    POST /users/{user_id}/password

Let’s use the Python shell to perform step-by-step task. As well a full script for this task can be created. For more examples of APIs usage from Python, please see here.

At first, we need to receive an authentication token and retrieve partner tenant ID - partner_id. More detailed information how to receive an authentication token can be found here and here.

As you expected to be a partner, you can retrieve a tenant id using
GET /users/me
request. It can be found in this tutorial.

Thus, you have the authentication token and the partner_id

>>> auth  # the 'Authorization' header value with the access token
{'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImMwMD...'}
>>> partner_id  # the UUID of the tenant to which the token provides access
'ede9f834-70b3-476c-83d9-736f9f8c7dae'

Creating a customer tenant in the partner tenant

Define a variable named customer, and then assign an object containing the minimum information about the customer tenant to this variable:

>>> customer = {
...     'name': 'Customer, Inc',
...     'kind': 'customer',
...     'parent_id': partner_id,
... }
Name Value type Required Description
name string Yes A tenant name.
kind string Yes A tenant type.
parent_id UUID string Yes The UUID of a tenant where this tenant will be created.

Convert the customer object to a JSON text:

>>> customer = json.dumps(customer, indent=4)
>>> print(customer)
{
    "name": "Customer, Inc",
    "kind": "customer",
    "parent_id": "ede9f834-70b3-476c-83d9-736f9f8c7dae"
}

Send a POST request with the JSON text to the /tenants endpoint:

>>> response = requests.post(
...     f'{base_url}/tenants',
...     headers={'Content-Type': 'application/json', auth},
...     data=customer,
... )

Check the status code of the response:

>>> response.status_code
201

The 201 code means that the platform has created the customer tenant in the trial mode.
A different code means that an error has occurred. For the details, refer to the “Errors” section of the endpoint.

Also, the response body contains the tenant information formatted as a JSON text. When converted to an object, it will look as follows:

>>> pprint.pprint(response.json())
{'ancestral_access': True,
 'brand_id': 3579,
 'contact': {...},
 'customer_id': None,
 'customer_type': 'default',
 'default_idp_id': '11111111-1111-1111-1111-111111111111',
 'enabled': True,
 'has_children': False,
 'id': '95303d96-628c-4265-9afa-07bee3fccf39',
 'internal_tag': None,
 'kind': 'customer',
 'language': 'en',
 'name': 'Customer, Inc',
 'owner_id': None,
 'parent_id': 'ede9f834-70b3-476c-83d9-736f9f8c7dae',
 'update_lock': {'enabled': False, 'owner_id': None},
 'version': 1}

Define a variable named customer_id, and then assign the UUID of the created tenant to this variable:

>>> customer_id = response.json()['id']
>>> customer_id
'95303d96-628c-4265-9afa-07bee3fccf39'

For more information about how the API represents tenants and what operations are available with them, see here.

Enabling offering items for the customer tenant

Fetch the list of offering items available for customer tenants of the partner tenant by sending a GET request to the /tenants/{partner_id}/offering_items/available_for_child endpoint. The endpoint URL should contain a kind query parameter set to customer:

>>> response = requests.get(
...     f'{base_url}/tenants/{partner_id}/offering_items/available_for_child',
...     headers=auth,
...     params={'kind': 'customer'},
... )

Check the status code of the response:

1.	>>> response.status_code
200

The 200 code means that the response body text contains an encoded JSON object consisting of the items member. The items member is an array of objects of offering items that can be enabled for customer tenants. If no items can be enabled, this array is empty.
A different code means that an error has occurred. For the details, refer to the “Errors” section of the endpoint.

Convert the JSON text to an object, and then store the value of the object's items key in a variable named offering_items:

>>> offering_items = response.json()['items']
>>> pprint.pprint(offering_items)
[{'application_id': '6e6d758d-8e74-3ae3-ac84-50eb0dff12eb',
  'infra_id': '019097a6-114f-4418-bd54-e01ef049f209',
  'locked': False,
  'measurement_unit': 'bytes',
  'name': 'storage',
  'quota': {'overage': None, 'value': None, 'version': 0},
  'status': 1,
  'type': 'infra'},
 {'application_id': '6e6d758d-8e74-3ae3-ac84-50eb0dff12eb',
  'locked': False,
  'measurement_unit': 'quantity',
  'name': 'workstations',
  'quota': {'overage': None, 'value': None, 'version': 0},
  'status': 1,
  'type': 'count'}, ...]

In this list, find the objects where the value of the name key is either workstations (the Workstations offering item) or storage (the Backup storage offering item) and store them in the following items object:

>>> item_names = {'workstations', 'storage'}
>>> items_to_enable = [item for item in offering_items if item['name'] in item_names]
>>> items = {'offering_items': items_to_enable}

Convert the items object to a JSON text:

>>> items = json.dumps(items, indent=4)

Send a PUT request with the JSON text to the /tenants/{customer_id}/offering_items endpoint:

>>> response = requests.put(
...     f'{base_url}/tenants/{customer_id}/offering_items',
...     headers={'Content-Type': 'application/json', auth},
...     data=items,
... )

Check the status code of the response:

>>> response.status_code
200

The 200 code means that the platform has enabled the backup service with the specified offering items for the tenant.
A different code means that an error has occurred. For the details, refer to the “Errors” section of the endpoint.

Creating a user account in the customer tenant

Define a variable named user_login, and then assign the login for a new user account to this variable:

>>> user_login = 'RichardDoe'

Check if this login is available in the platform. To do this, send a GET request to the /users/check_login endpoint. The endpoint URL should contain a username query parameter set to the login:

>>> response = requests.get(
...     f'{base_url}/users/check_login',
...     headers=auth,
...     params={'username': user_login},
... )

Check the status code of the response:

>>> response.status_code
204

The 204 code means that the login is not taken by any other account registered in the platform.
A different code means that an error has occurred. For the details, refer to the “Errors” section of the endpoint.

Define a variable named account, and then assign an object containing the minimum information about the user account to this variable:

>>> account = {
...     'tenant_id': customer_id,
...     'login': user_login,
...     'contact': {
...         'email': 'richard.doe@example.com',
...         'firstname': 'Richard',
...         'lastname': 'Doe',
...     },
... }
Name Value type Required Description
tenant_id string Yes The UUID of a tenant where an account will be created.
login string Yes An account login.
contact object Yes The contact information of an account.
email string Yes An email address that will be used for account activation and service notifications.
firstname string No The first name of a user.
lastname string No The last name of a user.

Convert the account object to a JSON text:

>>> account = json.dumps(account, indent=4)
>>> print(account)
{
    "tenant_id": "95303d96-628c-4265-9afa-07bee3fccf39",
    "login": "'RichardDoe'",
    "contact": {
        "email": "richard.doe@example.com",
        "firstname": "Richard",
        "lastname": "Doe"
    }
}

Send a POST request with the JSON text to the /users endpoint:

>>> response = requests.post(
...     f'{base_url}/users',
...     headers={'Content-Type': 'application/json', auth},
...     data=account,
... )

Check the status code of the response:

>>> response.status_code
200

The 200 code means that the platform has created, in the customer tenant, a non-activated user account with the RichardDoe login and a personal tenant. The personal tenant is for managing the account's offering item quotas. A different code means that an error has occurred. For the details, refer to the “Errors” section of the endpoint.

Also, the response body contains the account information formatted as a JSON text. When converted to an object, it will look as follows:

>>> pprint.pprint(response.json())
{'activated': False,
 'business_types': [],
 'contact': {'email': 'richard.doe@example.com',
             'firstname': 'Richard',
             'lastname': 'Doe', ...},
 'created_at': '2019-07-09T06:03:00.502053+00:00',
 'enabled': True,
 'id': 'fb00afe1-c8c5-43c6-9ca5-76ea33091715',
 'idp_id': '11111111-1111-1111-1111-111111111111',
 'language': 'en',
 'login': 'RichardDoe',
 'notifications': ['quota', 'reports', 'backup_daily_report'],
 'personal_tenant_id': 'b1ab6d40-f88e-46a3-9092-2aadeae0b888',
 'tenant_id': '95303d96-628c-4265-9afa-07bee3fccf39',
 'terms_accepted': False,
 'version': 1}

Define a variable named user_id, and then assign the UUID of the created user account to this variable:

>>> user_id = response.json()['id']
>>> user_id
'fb00afe1-c8c5-43c6-9ca5-76ea33091715'

For more information about how the API represents user accounts and what operations are available with them, see here.

Assigning the backup user role to the user account

Define a variable named roles, and then assign the following object containing the role information to this variable:

>>> roles = {
...     'items': [
...         {
...             'tenant_id': customer_id,
...             'trustee_id': user_id,
...             'role_id': 'backup_user',
...             'id': '00000000-0000-0000-0000-000000000000',
...             'issuer_id': '00000000-0000-0000-0000-000000000000',
...             'trustee_type': 'user',
...             'version': 0,
...         },
...     ],
... }
Name Value type Required Description
items array of objects Yes The list of roles to be assigned to an account.
tenant_id string Yes The UUID of a tenant where the account is registered.
trustee_id string Yes The account UUID.
role_id string Yes The internal name of a role.
id, issuer_id string Yes The value must be 00000000-0000-0000-0000-000000000000.
trustee_type string Yes The value must be user.
version number Yes The value must be 0.

Convert the roles object to a JSON text:

>>> roles = json.dumps(roles, indent=4)
>>> print(roles)
{
    "items": [
        {
            "tenant_id": "95303d96-628c-4265-9afa-07bee3fccf39",
            "trustee_id": "fb00afe1-c8c5-43c6-9ca5-76ea33091715",
            "role_id": "backup_user",
            "id": "00000000-0000-0000-0000-000000000000",
            "issuer_id": "00000000-0000-0000-0000-000000000000",
            "trustee_type": "user",
            "version": 0
        }
    ]
    }

Send a PUT request with the JSON text to the /users/{user_id}/access_policies endpoint:

>>> response = requests.put(
...     f'{base_url}/users/{user_id}/access_policies',
...     headers={'Content-Type': 'application/json', auth},
...     data=roles,
... )

Check the status code of the response:

>>> response.status_code
200

The 200 code means that the platform has assigned the specified role to the user account and enabled the backup service with the Workstations and Backup storage offering items for the account.
A different code means that an error has occurred. For the details, refer to the “Errors” section of the endpoint.

Activating the user account by setting its password

Define a variable named password, and then assign an object containing the password key with a password string to this variable:

>>> password = {'password': 'newUserPassword312'}

A password may be of any length and contain any unicode characters. If the password is empty, the user will be able to log in to the backup console by using a generated login URL only.

Convert the password object to a JSON text:

>>> password = json.dumps(password, indent=4)
>>> print(password)
{
    "password": "newUserPassword312"
}

Send a POST request with the JSON text to the /users/{user_id}/password endpoint:

>>> response = requests.post(
...     f'{base_url}/users/{user_id}/password',
...     headers={'Content-Type': 'application/json', auth},
...     data=password,
... )

Check the status code of the response:

>>> response.status_code
204

The 204 code means that the platform has set the specified password for the user account and activated this account.
So, we have created a customer, a user and enable backup for the user.

Summary

Now you know how to enable the backup service for a user in a customer tenant.
Start today, register on the Acronis Developer Portal and see the code samples available
Review solutions available in the Acronis Cyber Cloud Solutions Portal.

Updated
Stas Pavlov
Technology Evangelist
Cyber protection
Acronis Cyber Platform
Python code examples