High-Level Architecture

PSA Integration is a micro service that is deployed as a Helm release in a Kubernetes cluster with its own release cycle. It is not included in the CloudBlue Commerce distribution package by default and must be installed to the platform separately. One PSA integration instance serves all resellers on a CloudBlue Commerce instance.

The PSA Integration service consists of these three components:

  • The PSA Integration frontend that serves static content (scripts, images, and so on) and is hosted in a separate container on the service node with installed Kubernetes.
  • The PSA Integration backend that processes business logic requests from the frontend and is hosted in a separate container on the service node with installed Kubernetes.
  • The PSA Integration database that is hosted on any DB node and provided as a service.

mceclip0.png

Installation Prerequisites

Before installing the PSA Integration component, ensure that:

Network Requirements

The following ports must be open:

From

To

Protocol

Port

Purpose

Browser

CloudBlue Commerce brand

HTTPS

443

To display content in a user browser.

CloudBlue Commerce brand

Service of PSA Integration frontend

HTTP

80

To transfer static content (front-end scripts, images, and so on) to a user's browser.

CloudBlue Commerce brand

Service of PSA Integration backend

HTTP

8080

Transferring JSON data generated by business logic.

Service of PSA Integration backend

PSA Integration back-end pod

HTTP

8080

To pass internal K8s communication connections from the CloudBlue Commerce brand to the pod.

Service of PSA Integration frontend

PSA Integration front-end pod

HTTP

80

To pass internal K8s communication connections from the CloudBlue Commerce brand to the pod.

PSA Integration back-end pod

ConnectWise Management API

HTTPS

443

To transfer required ConnectWise data: product and customer identifiers, and so on.

PSA Integration back-end pod

PostgreSQL server

HTTPS

443

To store tasks, mappings and other PSA Integration-related entities.

PSA Integration back-end pod

CloudBlue Commerce brand

HTTPS

443

To transfer CloudBlue Commerce data related to orders and subscriptions.

 

Resource Requirements

PSA Integration consists of a back-end pod and a front-end pod. The minimum Kubernetes resources required for the micro service deployment, including on premise Kubernetes cluster deployment, are:

  • Back-end pod
  • requests:
  •   cpu: 1000m
  •   memory: 768Mi
  • limits:
  •   cpu: 4000m

  memory: {{ .Values.resources.limits.memory }}

Note: The memory limit is configurable. The default value is 1280Mi.

  • Front-end pod
  • requests:
  •   cpu: 100m
  •   memory: 512Mi
  • limits:
  •   cpu: 250m

  memory: 768M

Identity Service Requirements

PSA authenticates through Identity Service. Identity Service is required for each brand through which resellers access PSA. For information on how to create a Keycloak client for PSA, refer to Identity Service Management.

CloudBlue Commerce Brand Requirements

Resellers use their CloudBlue Commerce brands to access PSA. The PSA URL has the following format: https://${brand-domain}/${psa-url-suffix} (for example, https://cp.us.na.cloud.im/us-psa).

For each brand through which resellers access PSA, you must create rewrite rules:

  1. Download the psa-brand-rules-ctl.py script to the management node.
  2. Add rewrite rules to the selected brand:

# python psa-brand-rules-ctl.py add ${brand-id} [--psa_url_suffix ${psa-url-suffix}]

where:

    • ${brand-id}: the internal ID of the brand through which resellers access to PSA.
    • ${psa-url-suffix}: (optional) the PSA URL suffix. If you set this parameter, PSA will be available at the following address https://${brand-domain}/${psa-url-suffix} instead of https://${brand-domain}/psa. Refer to Log-in URLs.

APS API Access Requirements

A precreated L0 staff member with OAuth credentials must exist in the system. This staff member must have the following GET permissions for APS types:

This staff member must have this POST permission for an APS type:

These permissions are configured in the System > Users > staff_member > APS Bus Access tab.

psa-brand-rule-ctl.py


#!/usr/bin/env python
 
import argparse 
from poaupdater import openapi 
from poaupdater import uLogging 

uLogging.init2(log_file="/var/log/pa/psa-brand-rules-ctl.log", log_file_rotation=True, verbose=True)
 
DEFAULT_PSA_SUFFIX = "psa" 
REWRITE_RULES_TEMPLATE = """ 
# Begin of PSA Integration configuration 
RewriteRule ^psa/api/(.*)$ http://psa-integration-backend:8080/api/$1 [E=PSA:true,P] 
RequestHeader set X-Brand-ID {brand_id} env=PSA 
RequestHeader set X-Brand {brand_domain} env=PSA 
RewriteRule ^psa/?(.*)$ http://psa-integration-frontend/$1 [P] 
{custom_url_suffix_comment}RewriteRule ^{psa_url_suffix}/?(.*)$ http://psa-integration-frontend/$1 [P] 
# End of PSA Integration configuration """ 

def add(params):    
brand_id = params.brand_id    
psa_url_suffix = params.psa_url_suffix    
uLogging.info("Adding PSA rewrite rules to brand #%s" % brand_id)    
try:        
api = openapi.OpenAPI()        
brand_domain_name = api.pem.getBrandInfo(brand_id=brand_id)['domain_name']        
if not (psa_url_suffix and psa_url_suffix.strip()):            
raise Exception("PSA URL suffix could not be empty")        
custom_url_suffix_comment = "# " if psa_url_suffix == DEFAULT_PSA_SUFFIX else ""        
rewrite_rules = REWRITE_RULES_TEMPLATE.format(brand_id=brand_id, brand_domain=brand_domain_name,                                                      
custom_url_suffix_comment=custom_url_suffix_comment,                                                      
psa_url_suffix=psa_url_suffix)        
api.pem.addBrandRewriteRules(brand_id=brand_id, referer={'id': 0, 'domain': 'PSA', 'kind': 'PSA'}, rules=rewrite_rules)    
except (Exception, KeyboardInterrupt):        
uLogging.save_traceback()        
raise    
uLogging.info("PSA rewrite rules was added to brand #%s" % brand_id) 

def remove(params):    
brand_id = params.brand_id    
uLogging.info("Removing PSA 
rewrite rules from brand #%s" % brand_id)    
try:       
 api = openapi.OpenAPI()       
 api.pem.removeBrandRewriteRules(brand_id=brand_id, referer={'id': 0, 'domain': 'PSA', 'kind': 'PSA'})    
except (Exception, KeyboardInterrupt):       
 uLogging.save_traceback()        
raise    
uLogging.info("PSA rewrite rules was removed from brand #%s" % brand_id) 

if __name__ == '__main__':    
parser = argparse.ArgumentParser()    
subparsers = parser.add_subparsers(help='sub-command help')     
parser_add = subparsers.add_parser('add', help='Add rewrite rules')    
parser_add.add_argument("brand_id", type=int, help="Internal ID of the brand for which integration with PSA is configuring")    
parser_add.add_argument("--psa_url_suffix", default=DEFAULT_PSA_SUFFIX, help="PSA URL suffix")    
parser_add.set_defaults(func=add)     
parser_add = subparsers.add_parser('remove', help='Remove rewrite rules')    
parser_add.add_argument("brand_id", type=int, help="Internal ID of the brand for which integration with PSA is configuring")    
parser_add.set_defaults(func=remove)     
args = parser.parse_args()    
args.func(args)