Name

sn_itom_license.ITOMLicensingAccountingHelper

Description

Helper to account usage of each stream against the SKU s assigned to an instance

Script

var ITOMLicensingAccountingHelper = Class.create();

ITOMLicensingAccountingHelper.prototype = {
  initialize: function () {
  	this.streamCounts = {};
  	this.streamCountsPerDomain = [];
  	this.domainWiseAccountedRecs = [];

  	this.VISIBILITY = 'visibility';
  	this.HEALTH = 'health';
  	this.OPTIMIZATION = 'optimization';
  	this.OPTIMIZATIONv2 = 'optimizationv2';
  	this.DISCOVERY = 'discovery';
  	this.VALUE_STREAM = 'value_stream';
  	this.DOMAIN = 'sys_domain';
  	this.USAGE_ACCOUNT_TABLE = 'itom_lu_usage_account';
  	this.CI_COUNTS_TABLE = 'itom_lu_ci_counts';

  	this.bundle2Streams = {
  		'itom_standard': [this.VISIBILITY],
  		'itom_pro': [this.VISIBILITY, this.HEALTH],
  		'itom_enterprise': [this.VISIBILITY, this.HEALTH, this.OPTIMIZATION],
  		'itom_enterprisev2': [this.VISIBILITY, this.HEALTH, this.OPTIMIZATIONv2]
  	};

  	this.aLaCarteSKUs = {};

  	this.utils = new ITOMLicensingUtils();
  	this.arrayUtil = new global.ArrayUtil();
  	this.instanceSkuList = JSON.parse(sn_lef.GlideEntitlement.getUnifiedItomLicenseInfo());

  	// remove all existing accounting records
  	this._cleanup();
  },

  _cleanup: function() {
  	var cleanupGr = new GlideRecord(this.USAGE_ACCOUNT_TABLE);
  	cleanupGr.deleteMultiple();
  },

  _createAccountRecord: function (bundleId, skuUsage, domain) {
  	var skuAccounting = new GlideRecord(this.USAGE_ACCOUNT_TABLE);
  	skuAccounting.addQuery('sku_name', bundleId);
  	skuAccounting.addQuery(this.DOMAIN, domain);
  	skuAccounting.query();

  	gs.debug("creating accounting record for bundleId=" + bundleId + ", bundleUsageCount=" + skuUsage + " in domain " + domain);

  	if (skuAccounting.next()) {
  		skuAccounting.setValue('accounted_usage', skuUsage);
  		skuAccounting.setValue(this.DOMAIN, domain);
  		skuAccounting.update();
  	} else {
  		skuAccounting.setValue('sku_name', bundleId);
  		skuAccounting.setValue('accounted_usage', skuUsage);
  		skuAccounting.setValue(this.DOMAIN, domain);
  		skuAccounting.insert();
  	}

  	//}
  },

  _getHighWaterMark: function(bundleId) {
  	var highWaterMark = {};
  	highWaterMark.total = 0; // High watermark among all value streams
  	highWaterMark.nonALaCarte = 0; // High watermark among streams without purchased a la carte
  	var streams = this.bundle2Streams[bundleId];
  	for (var i = 0; i < streams.length; i++) {
  		var stream = streams[i];
  		var streamCount = this.streamCounts[stream];
  		if (!streamCount)
  			continue;

  		if (streamCount > highWaterMark.total) {
  			highWaterMark.bundleStream = stream;
  			highWaterMark.total = streamCount;
  		}

  		if (!this._findValidSku(stream)) {
  			if (streamCount > highWaterMark.nonALaCarte) {
  				highWaterMark.nonALacarteStream = stream;
  				highWaterMark.nonALaCarte = streamCount;
  			}
  		}
  	}

  	return highWaterMark;
  },

  _findValidSku: function(streamName) {
  	var sku = this.aLaCarteSKUs[streamName];
  	if (sku)
  		return sku.app_bundle;

  	return "";
  },

  accountUsage: function (isDomainSeparated) {
  	var streamAggregate = new GlideAggregate(this.CI_COUNTS_TABLE);
  	streamAggregate.addQuery('is_aggregated', 'true');
  	streamAggregate.addAggregate('SUM', 'su_count');
  	streamAggregate.groupBy(this.VALUE_STREAM);
  	streamAggregate.groupBy(this.DOMAIN);
  	streamAggregate.query();

  	while (streamAggregate.next()) {
  		var streamCountPerVS = {};
  		var totalUsage = parseInt(streamAggregate.getAggregate('SUM', 'su_count'));
  		streamCountPerVS.stream = streamAggregate.getValue(this.VALUE_STREAM).toLowerCase();
  		streamCountPerVS.stream = ((streamCountPerVS.stream == 'optimization' && this.utils.isOptV2SKUPresent()) ? 'optimizationv2' : streamCountPerVS.stream);
  		streamCountPerVS.domain = streamAggregate.getValue(this.DOMAIN).toLowerCase();
  		streamCountPerVS.usage = totalUsage;

  		this.streamCountsPerDomain.push(streamCountPerVS);

  		if (this.streamCounts[streamCountPerVS.stream]) {
  			this.streamCounts[streamCountPerVS.stream] += totalUsage;
  		} else {
  			this.streamCounts[streamCountPerVS.stream] = totalUsage;
  		}
  	}

  	gs.debug("initialized streamCounts=" + global.JSON.stringify(this.streamCounts));
  	gs.debug("starting accounting with license list=" + global.JSON.stringify(this.instanceSkuList));

  	/*
       * we should NEVER encounter a scenario where there are multiple bundles in the SKU list
       * a customer can have ONE bundle with as many a la carte SKU's as they desire
       * if multiple bundles are found, we'll consider only the first bundle
       */
  	var bundleSKU;

  	// Visibility and Discovery SKUs should not be together. If they're, then ignore Discovery SKU

  	for (var i = 0; i < this.instanceSkuList.length; i++) {
  		var sku = this.instanceSkuList[i];
  		var skuId = sku.app_bundle ? sku.app_bundle : "";

  		if (skuId == this.VISIBILITY || skuId == this.HEALTH || (skuId == this.OPTIMIZATION || skuId == this.OPTIMIZATIONv2) || (skuId == this.DISCOVERY && !this.utils.ignoreITOMDiscovery())) {
  			this.aLaCarteSKUs[skuId] = sku;
  			continue;
  		}

  		// bundle id's are one of the following -> itom_standard, itom_pro, itom_enterprise
  		if (this.bundle2Streams[skuId]) { // we found the first bundle SKU.. we account the usage against this bundle first
  			if (bundleSKU) {
  				// we found another bundle in the SKU's assigned to this instance.. skip it, log a warning
  				gs.warn('Skipping additional ITOM bundle SKU assigned to the instance : ' + skuId);
  			} else {
  				bundleSKU = sku;
  			}

  			continue;
  		}

  		gs.warn('Skipping invalid ITOM license assigned to the instance : ' + skuId);
  	}

  	if (bundleSKU) {
  		var bundleId = bundleSKU.app_bundle;
  		var highWaterMark = this._getHighWaterMark(bundleId);

  		// only bundle SKU assigned to this instance.. fill the bundle up and exit
  		if (Object.keys(this.aLaCarteSKUs).length == 0) {
  			// use the high watermark as the bundle count and return
  			this._createAccountRecord(bundleId, highWaterMark.total, 'global'); // create the bundle usage record
  			gs.debug('done accounting against bundle.. within bundle usage limits..');
  			this._doDomainWiseAccounting(highWaterMark.bundleStream, bundleId, isDomainSeparated);
  			return;
  		}

  		// more than 1 SKU assigned.. start with checking the usage against the bundle
  		var purchased_count = bundleSKU.purchased_count;
  		var streams = this.bundle2Streams[bundleId];

  		var bundleUsageCount = (highWaterMark.total <= purchased_count) ? highWaterMark.total :
  		Math.max(highWaterMark.nonALaCarte, purchased_count);

  		var bundleUsageStream;
  		if (highWaterMark.total <= purchased_count) {
  			bundleUsageStream = highWaterMark.bundleStream;
  		} else if (highWaterMark.nonALaCarte <= purchased_count) {
  			bundleUsageStream = highWaterMark.bundleStream;
  		} else {
  			bundleUsageStream = highWaterMark.nonALacarteStream;
  		}

  		for (i = 0; i < streams.length; i++) {
  			var stream = streams[i];
  			var streamCount = this.streamCounts[stream];
  			if (!streamCount)
  				continue;

  			// adjust stream usage if there are valid SKU's available
  			if (streamCount > bundleUsageCount) {
  				if (this._findValidSku(stream) != '') {
  					this.streamCounts[stream] -= bundleUsageCount;
  				}
  			} else {
  				this.streamCounts[stream] = 0; // reduce the usage to 0 since we are within limits here
  			}
  		}

  		// create the bundle usage record
  		this._createAccountRecord(bundleId, bundleUsageCount, 'global');
  		gs.debug('done accounting against bundle.. accounting against a la carte sku next...');

  	}

  	gs.debug("pending allocation=" + global.JSON.stringify(this.streamCounts));

  	// account for the remaining usage from the value streams against the available SKU's
  	for (var j = 0; j < Object.keys(this.aLaCarteSKUs).length; j++) {
  		var skuId = Object.keys(this.aLaCarteSKUs)[j];
  		if (this.streamCounts[skuId] > 0) {
  			this._createAccountRecord(skuId, this.streamCounts[skuId], 'global');
  		}
  	}

  	this._doDomainWiseAccounting(bundleUsageStream, bundleId, isDomainSeparated);
  },

  _doDomainWiseAccounting: function (bundleUsageStream, bundleId, isDomainSeparated) {
  	if (!isDomainSeparated)
  		return;

  	gs.debug("ITOMLicensingAccountingHelper: Starting domain wise accounting ...");
  	var mspHelper = new sn_itom_license.ITOMLicensingMSPHelper(this.streamCountsPerDomain, bundleUsageStream, this._getNonHighwatermarkStreamsInBundle(bundleUsageStream, bundleId));
  	var domainWiseAccountingRecs = mspHelper.accountUsageForDomains();
  	if (domainWiseAccountingRecs && domainWiseAccountingRecs.length > 0){
  		this._createDomainWiseAccountRecord(domainWiseAccountingRecs);
  	} else {
  		// No domain wise data. Either every domain is reporting zero usage or
  		// there is no license applied. Check for SKUs in instance to see if any license
  		// is applied and if so, report zero usage for that SKU for all domains
  		this._populateZeroUsageRecs(this._getSkusToPopulateFromInstance());
  	}

  	gs.debug("ITOMLicensingAccountingHelper: Domain wise accounting completed.");
  },

  _getSkusToPopulateFromInstance : function(){
  	var skusToPopulate = [];
  	for (var i = 0; i < this.instanceSkuList.length; i++){
  		var sku = this.instanceSkuList[i];
  		if (sku && this._isValidSKU(sku.app_bundle)){
  			skusToPopulate.push(sku.app_bundle);
  		}
  	}

  	return skusToPopulate;
  },

  _isValidSKU: function(sku){
  	return (this._isBundleSKU(sku) || this._isAlacarteSKU(sku));
  },

  _isBundleSKU: function(sku){
  	return this.bundle2Streams[sku];
  },

  _isAlacarteSKU: function(sku){
  	return this.arrayUtil.contains([ this.VISIBILITY , this.HEALTH, this.OPTIMIZATION, this.DISCOVERY ], sku);
  },

  _getNonHighwatermarkStreamsInBundle : function(bundleUsageStream, bundleId){
  	var nonHighwatermarkStreams = [];
  	var streamsInTheBundle = this.bundle2Streams[bundleId];
  	if (streamsInTheBundle){
  		for (var i = 0; i < streamsInTheBundle.length; i++){
  			var eachStream = streamsInTheBundle[i];
  			if (eachStream != bundleUsageStream){
  				nonHighwatermarkStreams.push(eachStream);
  			}
  		}
  	}

  	return nonHighwatermarkStreams;
  },

  _createDomainWiseAccountRecord: function (domainWiseAccountingRecs) {
  	var skusToPopulate = [];
  	for (var i = 0; i < domainWiseAccountingRecs.length; i++) {
  		var record = domainWiseAccountingRecs[i];
  		if (record.usage > 0){
  			this._createAccountRecord(record.sku, record.usage, record.domain);
  			this.domainWiseAccountedRecs.push({'domain': record.domain, 'sku': record.sku});
  			if (!skusToPopulate || !this.arrayUtil.contains(skusToPopulate, record.sku))
  				skusToPopulate.push(record.sku);
  		}
  	}

  	this._populateZeroUsageRecs(skusToPopulate);

  	var domainWithNonZeroUsage = this._getDomainsWithNonZeroUsage();
  	if (this._shouldCleanGlobalRecords(domainWithNonZeroUsage))
  		this._cleanGlobalRecords();
  },

  _shouldCleanGlobalRecords: function(domainWithNonZeroUsage){
  	// Clean global records if you have at least one domain other than global which has non-zero usage
  	return (domainWithNonZeroUsage && (domainWithNonZeroUsage.length > 1 || (domainWithNonZeroUsage.length == 1 && domainWithNonZeroUsage[0] != 'global')));
  },

  _getDomainsWithNonZeroUsage: function(){
  	var domainWithNonZeroUsage = [];
  	if (this.domainWiseAccountedRecs){
  		for (var i = 0 ; i < this.domainWiseAccountedRecs.length; i++){
  			var accountedRec = this.domainWiseAccountedRecs[i];
  			if (accountedRec.usage > 0)
  				domainWithNonZeroUsage.push(accountedRec.domain);
  		}
  	}

  	return domainWithNonZeroUsage;
  },

  _populateZeroUsageRecs: function(skusToPopulate){
  	var allDomains = this.utils.getDomains();
  	for (var i = 0; i < skusToPopulate.length; i++) {
  		var sku = skusToPopulate[i];
  		for (var j = 0; j < allDomains.length; j++){
  			var domain = allDomains[j];
  			if (!this._isAlreadyAccounted(domain, sku)){
  				this._createAccountRecord(sku, 0, domain);
  			}
  		}
  	}
  },

  _isAlreadyAccounted: function(domain, sku){
  	if (this.domainWiseAccountedRecs){
  		for (var i = 0 ; i < this.domainWiseAccountedRecs.length; i++){
  			var accountedRec = this.domainWiseAccountedRecs[i];
  			if (accountedRec.sku == sku && accountedRec.domain == domain)
  				return true;
  		}
  	}

  	return false;
  },

  _cleanGlobalRecords: function () {
  	var usageCleanerGR = new GlideRecord('itom_lu_usage_account');
  	usageCleanerGR.addQuery(this.DOMAIN, 'global');
  	usageCleanerGR.query();
  	usageCleanerGR.deleteMultiple();
  },

  type: 'ITOMLicensingAccountingHelper'
};

Sys ID

f59452fbdb803300c7817f6bbf9619cd

Offical Documentation

Official Docs: