From 2dcfe98ec940932372855643f996b87c27f4b7fa Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Tue, 1 Feb 2022 15:20:54 +0100 Subject: [PATCH] Unify supervision urls setup and distribution to charging stations MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- README.md | 6 +-- src/assets/config-template.json | 2 +- src/charging-station/ChargingStation.ts | 51 ++++++++++++++++++++----- src/types/ChargingStationTemplate.ts | 2 +- src/types/ConfigurationData.ts | 10 ++++- src/utils/Configuration.ts | 11 +++--- 6 files changed, 61 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6dc4574f..9f1156e7 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,8 @@ But the modifications to test have to be done to the files in the build result d Key | Value(s) | Default Value | Value type | Description --- | -------| --------------| ---------- | ------------ -supervisionUrls | | [] | string[] | array of connection URIs to OCPP-J servers -distributeStationsToTenantsEqually | true/false | true | boolean | distribute charging stations uniformly to the OCPP-J servers +supervisionUrls | | [] | string \| string[] | string or array of global connection URIs to OCPP-J servers +supervisionUrlDistribution | round-robin/random/sequential | round-robin | boolean | supervision urls distribution policy to simulated charging stations workerProcess | workerSet/staticPool/dynamicPool | workerSet | string | worker threads process type workerStartDelay | | 500 | integer | milliseconds to wait at charging station worker threads startup workerPoolMinSize | | 4 | integer | worker threads pool minimum number of threads @@ -84,7 +84,7 @@ stationTemplateUrls | | {}[] | { file: string; numberOfStations: number; }[] | a Key | Value(s) | Default Value | Value type | Description --- | -------| --------------| ---------- | ------------ -supervisionUrl | | '' | string | connection URI to OCPP-J server +supervisionUrls | | '' | string \| string[] | string or array of connection URIs to OCPP-J servers. It has priority over the global configuration parameter supervisionUser | | '' | string | basic HTTP authentication user to OCPP-J server supervisionPassword | | '' | string | basic HTTP authentication password to OCPP-J server supervisionUrlOcppConfiguration | true/false | false | boolean | Allow supervision URL configuration via a vendor OCPP parameter key diff --git a/src/assets/config-template.json b/src/assets/config-template.json index a0c0526a..4520a2d4 100644 --- a/src/assets/config-template.json +++ b/src/assets/config-template.json @@ -2,7 +2,7 @@ "supervisionUrls": [ "ws://localhost:8010/OCPP16/5be7fb271014d90008992f06" ], - "distributeStationsToTenantsEqually": true, + "supervisionUrlDistribution": "sequential", "performanceStorage": { "enabled": true, "type": "jsonfile" diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index ad4dac38..4f6c7771 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -31,6 +31,7 @@ import { OCPPVersion } from '../types/ocpp/OCPPVersion'; import PerformanceStatistics from '../performance/PerformanceStatistics'; import { SampledValueTemplate } from '../types/MeasurandPerPhaseSampledValueTemplates'; import { StopTransactionReason } from '../types/ocpp/Transaction'; +import { SupervisionUrlDistribution } from '../types/ConfigurationData'; import { URL } from 'url'; import Utils from '../utils/Utils'; import crypto from 'crypto'; @@ -461,6 +462,10 @@ export default class ChargingStation { } catch (error) { FileUtils.handleFileException(this.logPrefix(), 'Template', this.stationTemplateFile, error as NodeJS.ErrnoException); } + const chargingStationId = this.getChargingStationId(stationTemplateFromFile); + // Deprecation template keys section + this.warnDeprecatedTemplateKey(stationTemplateFromFile, 'supervisionUrl', chargingStationId, 'Use \'supervisionUrls\' instead'); + this.convertDeprecatedTemplateKey(stationTemplateFromFile, 'supervisionUrl', 'supervisionUrls'); const stationInfo: ChargingStationInfo = stationTemplateFromFile ?? {} as ChargingStationInfo; stationInfo.wsOptions = stationTemplateFromFile?.wsOptions ?? {}; if (!Utils.isEmptyArray(stationTemplateFromFile.power)) { @@ -477,7 +482,7 @@ export default class ChargingStation { } delete stationInfo.power; delete stationInfo.powerUnit; - stationInfo.chargingStationId = this.getChargingStationId(stationTemplateFromFile); + stationInfo.chargingStationId = chargingStationId; stationInfo.resetTime = stationTemplateFromFile.resetTime ? stationTemplateFromFile.resetTime * 1000 : Constants.CHARGING_STATION_DEFAULT_RESET_TIME; return stationInfo; } @@ -933,17 +938,45 @@ export default class ChargingStation { } } + private warnDeprecatedTemplateKey(template: ChargingStationTemplate, key: string, chargingStationId: string, logMsgToAppend = ''): void { + if (!Utils.isUndefined(template[key])) { + logger.warn(`${Utils.logPrefix(` ${chargingStationId} |`)} Deprecated template key '${key}' usage in file '${this.stationTemplateFile}'${logMsgToAppend && '. ' + logMsgToAppend}`); + } + } + + private convertDeprecatedTemplateKey(template: ChargingStationTemplate, deprecatedKey: string, key: string): void { + if (!Utils.isUndefined(template[deprecatedKey])) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + template[key] = template[deprecatedKey]; + delete template[deprecatedKey]; + } + } + private getConfiguredSupervisionUrl(): URL { - const supervisionUrls = Utils.cloneObject(this.stationInfo.supervisionUrl ?? Configuration.getSupervisionUrls()); - let indexUrl = 0; + const supervisionUrls = Utils.cloneObject(this.stationInfo.supervisionUrls ?? Configuration.getSupervisionUrls()); if (!Utils.isEmptyArray(supervisionUrls)) { - if (Configuration.getDistributeStationsToTenantsEqually()) { - indexUrl = this.index % supervisionUrls.length; - } else { - // Get a random url - indexUrl = Math.floor(Utils.secureRandom() * supervisionUrls.length); + let urlIndex = 0; + switch (Configuration.getSupervisionUrlDistribution()) { + case SupervisionUrlDistribution.ROUND_ROBIN: + urlIndex = (this.index - 1) % supervisionUrls.length; + break; + case SupervisionUrlDistribution.RANDOM: + // Get a random url + urlIndex = Math.floor(Utils.secureRandom() * supervisionUrls.length); + break; + case SupervisionUrlDistribution.SEQUENTIAL: + if (this.index <= supervisionUrls.length) { + urlIndex = this.index - 1; + } else { + logger.warn(`${this.logPrefix()} No more configured supervision urls available, using the first one`); + } + break; + default: + logger.error(`${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}', defaulting to ${SupervisionUrlDistribution.ROUND_ROBIN}`); + urlIndex = (this.index - 1) % supervisionUrls.length; + break; } - return new URL(supervisionUrls[indexUrl]); + return new URL(supervisionUrls[urlIndex]); } return new URL(supervisionUrls as string); } diff --git a/src/types/ChargingStationTemplate.ts b/src/types/ChargingStationTemplate.ts index 5d553318..c06468e9 100644 --- a/src/types/ChargingStationTemplate.ts +++ b/src/types/ChargingStationTemplate.ts @@ -35,7 +35,7 @@ export interface AutomaticTransactionGenerator { } export default interface ChargingStationTemplate { - supervisionUrl?: string; + supervisionUrls?: string | string[]; supervisionUrlOcppConfiguration?: boolean; supervisionUrlOcppKey?: string; supervisionUser?: string; diff --git a/src/types/ConfigurationData.ts b/src/types/ConfigurationData.ts index 51eb045a..56ee0354 100644 --- a/src/types/ConfigurationData.ts +++ b/src/types/ConfigurationData.ts @@ -4,6 +4,12 @@ import type { WorkerChoiceStrategy } from 'poolifier'; import { WorkerProcessType } from './Worker'; import { level } from 'winston'; +export enum SupervisionUrlDistribution { + ROUND_ROBIN = 'round-robin', + RANDOM = 'random', + SEQUENTIAL = 'sequential', +} + export interface StationTemplateUrl { file: string; numberOfStations: number; @@ -21,12 +27,12 @@ export interface StorageConfiguration { } export default interface ConfigurationData { - supervisionUrls?: string[]; + supervisionUrls?: string | string[]; + supervisionUrlDistribution?: SupervisionUrlDistribution; stationTemplateUrls: StationTemplateUrl[]; uiWebSocketServer?: UIWebSocketServerConfiguration; performanceStorage?: StorageConfiguration; autoReconnectMaxRetries?: number; - distributeStationsToTenantsEqually?: boolean; workerProcess?: WorkerProcessType; workerStartDelay?: number; workerPoolMinSize?: number; diff --git a/src/utils/Configuration.ts b/src/utils/Configuration.ts index 61fe300b..b1bc6e3a 100644 --- a/src/utils/Configuration.ts +++ b/src/utils/Configuration.ts @@ -1,4 +1,4 @@ -import ConfigurationData, { StationTemplateUrl, StorageConfiguration, UIWebSocketServerConfiguration } from '../types/ConfigurationData'; +import ConfigurationData, { StationTemplateUrl, StorageConfiguration, SupervisionUrlDistribution, UIWebSocketServerConfiguration } from '../types/ConfigurationData'; import Constants from './Constants'; import { ServerOptions } from 'ws'; @@ -150,16 +150,17 @@ export default class Configuration { return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logErrorFile') ? Configuration.getConfig().logErrorFile : 'error.log'; } - static getSupervisionUrls(): string[] { + static getSupervisionUrls(): string | string[] { Configuration.warnDeprecatedConfigurationKey('supervisionURLs', null, 'Use \'supervisionUrls\' instead'); !Configuration.isUndefined(Configuration.getConfig()['supervisionURLs']) && (Configuration.getConfig().supervisionUrls = Configuration.getConfig()['supervisionURLs'] as string[]); // Read conf return Configuration.getConfig().supervisionUrls; } - static getDistributeStationsToTenantsEqually(): boolean { - Configuration.warnDeprecatedConfigurationKey('distributeStationToTenantEqually', null, 'Use \'distributeStationsToTenantsEqually\' instead'); - return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'distributeStationsToTenantsEqually') ? Configuration.getConfig().distributeStationsToTenantsEqually : true; + static getSupervisionUrlDistribution(): SupervisionUrlDistribution { + Configuration.warnDeprecatedConfigurationKey('distributeStationToTenantEqually', null, 'Use \'supervisionUrlDistribution\' instead'); + Configuration.warnDeprecatedConfigurationKey('distributeStationsToTenantsEqually', null, 'Use \'supervisionUrlDistribution\' instead'); + return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'supervisionUrlDistribution') ? Configuration.getConfig().supervisionUrlDistribution : SupervisionUrlDistribution.ROUND_ROBIN; } private static logPrefix(): string { -- 2.34.1