From f911a4af34e676a49f542aec31cbef8075fb65ef Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 26 Mar 2023 15:08:37 +0200 Subject: [PATCH] refactor(simulator): cleanup id tags cache namespace MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/charging-station/AuthorizedTagsCache.ts | 172 ------------------ .../AutomaticTransactionGenerator.ts | 8 +- src/charging-station/ChargingStation.ts | 15 +- src/charging-station/IdTagsCache.ts | 170 +++++++++++++++++ src/charging-station/internal.ts | 2 +- .../ocpp/1.6/OCPP16IncomingRequestService.ts | 8 +- .../ocpp/1.6/OCPP16ResponseService.ts | 2 +- .../ocpp/OCPPIncomingRequestService.ts | 2 +- src/types/ChargingStationTemplate.ts | 1 + 9 files changed, 186 insertions(+), 194 deletions(-) delete mode 100644 src/charging-station/AuthorizedTagsCache.ts create mode 100644 src/charging-station/IdTagsCache.ts diff --git a/src/charging-station/AuthorizedTagsCache.ts b/src/charging-station/AuthorizedTagsCache.ts deleted file mode 100644 index edb69538..00000000 --- a/src/charging-station/AuthorizedTagsCache.ts +++ /dev/null @@ -1,172 +0,0 @@ -import fs from 'node:fs'; - -import { type ChargingStation, ChargingStationUtils } from './internal'; -import { FileType, IdTagDistribution } from '../types'; -import { FileUtils, Utils, logger } from '../utils'; - -type TagsCacheValueType = { - tags: string[]; - tagsFileWatcher: fs.FSWatcher | undefined; -}; - -export class AuthorizedTagsCache { - private static instance: AuthorizedTagsCache | null = null; - private readonly tagsCaches: Map; - private readonly tagsCachesAddressableIndexes: Map; - - private constructor() { - this.tagsCaches = new Map(); - this.tagsCachesAddressableIndexes = new Map(); - } - - public static getInstance(): AuthorizedTagsCache { - if (AuthorizedTagsCache.instance === null) { - AuthorizedTagsCache.instance = new AuthorizedTagsCache(); - } - return AuthorizedTagsCache.instance; - } - - public getIdTag( - distribution: IdTagDistribution, - chargingStation: ChargingStation, - connectorId: number - ): string { - const hashId = chargingStation.stationInfo.hashId; - const authorizationFile = ChargingStationUtils.getAuthorizationFile( - chargingStation.stationInfo - ); - switch (distribution) { - case IdTagDistribution.RANDOM: - return this.getRandomIdTag(hashId, authorizationFile); - case IdTagDistribution.ROUND_ROBIN: - return this.getRoundRobinIdTag(hashId, authorizationFile); - case IdTagDistribution.CONNECTOR_AFFINITY: - return this.getConnectorAffinityIdTag(chargingStation, connectorId); - default: - return this.getRoundRobinIdTag(hashId, authorizationFile); - } - } - - public getAuthorizedTags(file: string): string[] | undefined { - if (this.hasTags(file) === false) { - this.setTags(file, this.getAuthorizedTagsFromFile(file)); - } - return this.getTags(file); - } - - public deleteAuthorizedTags(file: string): boolean { - return this.deleteTags(file); - } - - private getRandomIdTag(hashId: string, file: string): string { - const tags = this.getAuthorizedTags(file); - const addressableKey = file + hashId; - this.tagsCachesAddressableIndexes.set( - addressableKey, - Math.floor(Utils.secureRandom() * tags.length) - ); - return tags[this.tagsCachesAddressableIndexes.get(addressableKey)]; - } - - private getRoundRobinIdTag(hashId: string, file: string): string { - const tags = this.getAuthorizedTags(file); - const addressableKey = file + hashId; - const idTagIndex = this.tagsCachesAddressableIndexes.get(addressableKey) ?? 0; - const idTag = tags[idTagIndex]; - this.tagsCachesAddressableIndexes.set( - addressableKey, - idTagIndex === tags.length - 1 ? 0 : idTagIndex + 1 - ); - return idTag; - } - - private getConnectorAffinityIdTag(chargingStation: ChargingStation, connectorId: number): string { - const file = ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo); - const tags = this.getAuthorizedTags(file); - const hashId = chargingStation.stationInfo.hashId; - const addressableKey = file + hashId; - this.tagsCachesAddressableIndexes.set( - addressableKey, - (chargingStation.index - 1 + (connectorId - 1)) % tags.length - ); - return tags[this.tagsCachesAddressableIndexes.get(addressableKey)]; - } - - private hasTags(file: string): boolean { - return this.tagsCaches.has(file); - } - - private setTags(file: string, tags: string[]) { - return this.tagsCaches.set(file, { - tags, - tagsFileWatcher: FileUtils.watchJsonFile( - file, - FileType.Authorization, - this.logPrefix(file), - undefined, - (event, filename) => { - if (Utils.isNotEmptyString(filename) && event === 'change') { - try { - logger.debug( - `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload` - ); - this.deleteTags(file); - this.deleteTagsIndexes(file); - } catch (error) { - FileUtils.handleFileException( - file, - FileType.Authorization, - error as NodeJS.ErrnoException, - this.logPrefix(file), - { - throwError: false, - } - ); - } - } - } - ), - }); - } - - private getTags(file: string): string[] | undefined { - return this.tagsCaches.get(file)?.tags; - } - - private deleteTags(file: string): boolean { - this.tagsCaches.get(file)?.tagsFileWatcher?.close(); - return this.tagsCaches.delete(file); - } - - private deleteTagsIndexes(file: string): void { - for (const [key] of this.tagsCachesAddressableIndexes) { - if (key.startsWith(file)) { - this.tagsCachesAddressableIndexes.delete(key); - } - } - } - - private getAuthorizedTagsFromFile(file: string): string[] { - let authorizedTags: string[] = []; - if (file) { - try { - // Load authorization file - authorizedTags = JSON.parse(fs.readFileSync(file, 'utf8')) as string[]; - } catch (error) { - FileUtils.handleFileException( - file, - FileType.Authorization, - error as NodeJS.ErrnoException, - this.logPrefix(file) - ); - } - } else { - logger.info(`${this.logPrefix(file)} No authorization file given`); - } - return authorizedTags; - } - - private logPrefix = (file: string): string => { - return Utils.logPrefix(` Authorized tags cache for authorization file '${file}' |`); - }; -} diff --git a/src/charging-station/AutomaticTransactionGenerator.ts b/src/charging-station/AutomaticTransactionGenerator.ts index aa2012df..9728108b 100644 --- a/src/charging-station/AutomaticTransactionGenerator.ts +++ b/src/charging-station/AutomaticTransactionGenerator.ts @@ -2,7 +2,7 @@ import { AsyncResource } from 'node:async_hooks'; -import { AuthorizedTagsCache, type ChargingStation, ChargingStationUtils } from './internal'; +import { type ChargingStation, ChargingStationUtils, IdTagsCache } from './internal'; import { BaseError } from '../exception'; // import { PerformanceStatistics } from '../performance'; import { PerformanceStatistics } from '../performance/PerformanceStatistics'; @@ -33,7 +33,6 @@ export class AutomaticTransactionGenerator extends AsyncResource { public readonly configuration: AutomaticTransactionGeneratorConfiguration; public started: boolean; private readonly chargingStation: ChargingStation; - private idTagIndex: number; private constructor( automaticTransactionGeneratorConfiguration: AutomaticTransactionGeneratorConfiguration, @@ -43,7 +42,6 @@ export class AutomaticTransactionGenerator extends AsyncResource { this.started = false; this.configuration = automaticTransactionGeneratorConfiguration; this.chargingStation = chargingStation; - this.idTagIndex = 0; this.connectorsStatus = new Map(); this.initializeConnectorsStatus(); } @@ -323,8 +321,8 @@ export class AutomaticTransactionGenerator extends AsyncResource { const measureId = 'StartTransaction with ATG'; const beginId = PerformanceStatistics.beginMeasure(measureId); let startResponse: StartTransactionResponse; - if (this.chargingStation.hasAuthorizedTags()) { - const idTag = AuthorizedTagsCache.getInstance().getIdTag( + if (this.chargingStation.hasIdTags()) { + const idTag = IdTagsCache.getInstance().getIdTag( this.configuration?.idTagDistribution, this.chargingStation, connectorId diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 81170bad..cc717711 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -10,11 +10,11 @@ import merge from 'just-merge'; import WebSocket, { type RawData } from 'ws'; import { - AuthorizedTagsCache, AutomaticTransactionGenerator, ChargingStationConfigurationUtils, ChargingStationUtils, ChargingStationWorkerBroadcastChannel, + IdTagsCache, MessageChannelUtils, SharedLRUCache, } from './internal'; @@ -103,7 +103,7 @@ export class ChargingStation { public stationInfo!: ChargingStationInfo; public started: boolean; public starting: boolean; - public authorizedTagsCache: AuthorizedTagsCache; + public idTagsCache: IdTagsCache; public automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined; public ocppConfiguration!: ChargingStationOcppConfiguration | undefined; public wsConnection!: WebSocket | null; @@ -141,7 +141,7 @@ export class ChargingStation { this.requests = new Map(); this.messageBuffer = new Set(); this.sharedLRUCache = SharedLRUCache.getInstance(); - this.authorizedTagsCache = AuthorizedTagsCache.getInstance(); + this.idTagsCache = IdTagsCache.getInstance(); this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this); this.initialize(); @@ -172,12 +172,9 @@ export class ChargingStation { ); }; - public hasAuthorizedTags(): boolean { - return Utils.isNotEmptyArray( - this.authorizedTagsCache.getAuthorizedTags( - ChargingStationUtils.getAuthorizationFile(this.stationInfo) - ) - ); + public hasIdTags(): boolean { + const idTagsFile = ChargingStationUtils.getAuthorizationFile(this.stationInfo); + return Utils.isNotEmptyArray(this.idTagsCache.getIdTags(idTagsFile)); } public getEnableStatistics(): boolean { diff --git a/src/charging-station/IdTagsCache.ts b/src/charging-station/IdTagsCache.ts new file mode 100644 index 00000000..7b49b590 --- /dev/null +++ b/src/charging-station/IdTagsCache.ts @@ -0,0 +1,170 @@ +import fs from 'node:fs'; + +import { type ChargingStation, ChargingStationUtils } from './internal'; +import { FileType, IdTagDistribution } from '../types'; +import { FileUtils, Utils, logger } from '../utils'; + +type IdTagsCacheValueType = { + idTags: string[]; + idTagsFileWatcher: fs.FSWatcher | undefined; +}; + +export class IdTagsCache { + private static instance: IdTagsCache | null = null; + private readonly idTagsCaches: Map; + private readonly idTagsCachesAddressableIndexes: Map; + + private constructor() { + this.idTagsCaches = new Map(); + this.idTagsCachesAddressableIndexes = new Map(); + } + + public static getInstance(): IdTagsCache { + if (IdTagsCache.instance === null) { + IdTagsCache.instance = new IdTagsCache(); + } + return IdTagsCache.instance; + } + + public getIdTag( + distribution: IdTagDistribution, + chargingStation: ChargingStation, + connectorId: number + ): string { + const hashId = chargingStation.stationInfo.hashId; + const idTagsFile = ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo); + switch (distribution) { + case IdTagDistribution.RANDOM: + return this.getRandomIdTag(hashId, idTagsFile); + case IdTagDistribution.ROUND_ROBIN: + return this.getRoundRobinIdTag(hashId, idTagsFile); + case IdTagDistribution.CONNECTOR_AFFINITY: + return this.getConnectorAffinityIdTag(chargingStation, connectorId); + default: + return this.getRoundRobinIdTag(hashId, idTagsFile); + } + } + + public getIdTags(file: string): string[] | undefined { + if (this.hasIdTagsCache(file) === false) { + this.setIdTagsCache(file, this.getIdTagsFromFile(file)); + } + return this.getIdTagsCache(file); + } + + public deleteIdTags(file: string): boolean { + return this.deleteIdTagsCache(file); + } + + private getRandomIdTag(hashId: string, file: string): string { + const idTags = this.getIdTags(file); + const addressableKey = file + hashId; + this.idTagsCachesAddressableIndexes.set( + addressableKey, + Math.floor(Utils.secureRandom() * idTags.length) + ); + return idTags[this.idTagsCachesAddressableIndexes.get(addressableKey)]; + } + + private getRoundRobinIdTag(hashId: string, file: string): string { + const idTags = this.getIdTags(file); + const addressableKey = file + hashId; + const idTagIndex = this.idTagsCachesAddressableIndexes.get(addressableKey) ?? 0; + const idTag = idTags[idTagIndex]; + this.idTagsCachesAddressableIndexes.set( + addressableKey, + idTagIndex === idTags.length - 1 ? 0 : idTagIndex + 1 + ); + return idTag; + } + + private getConnectorAffinityIdTag(chargingStation: ChargingStation, connectorId: number): string { + const file = ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo); + const idTags = this.getIdTags(file); + const hashId = chargingStation.stationInfo.hashId; + const addressableKey = file + hashId; + this.idTagsCachesAddressableIndexes.set( + addressableKey, + (chargingStation.index - 1 + (connectorId - 1)) % idTags.length + ); + return idTags[this.idTagsCachesAddressableIndexes.get(addressableKey)]; + } + + private hasIdTagsCache(file: string): boolean { + return this.idTagsCaches.has(file); + } + + private setIdTagsCache(file: string, idTags: string[]) { + return this.idTagsCaches.set(file, { + idTags, + idTagsFileWatcher: FileUtils.watchJsonFile( + file, + FileType.Authorization, + this.logPrefix(file), + undefined, + (event, filename) => { + if (Utils.isNotEmptyString(filename) && event === 'change') { + try { + logger.debug( + `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload` + ); + this.deleteIdTagsCache(file); + this.deleteIdTagsCacheIndexes(file); + } catch (error) { + FileUtils.handleFileException( + file, + FileType.Authorization, + error as NodeJS.ErrnoException, + this.logPrefix(file), + { + throwError: false, + } + ); + } + } + } + ), + }); + } + + private getIdTagsCache(file: string): string[] | undefined { + return this.idTagsCaches.get(file)?.idTags; + } + + private deleteIdTagsCache(file: string): boolean { + this.idTagsCaches.get(file)?.idTagsFileWatcher?.close(); + return this.idTagsCaches.delete(file); + } + + private deleteIdTagsCacheIndexes(file: string): void { + for (const [key] of this.idTagsCachesAddressableIndexes) { + if (key.startsWith(file)) { + this.idTagsCachesAddressableIndexes.delete(key); + } + } + } + + private getIdTagsFromFile(file: string): string[] { + let idTags: string[] = []; + if (file) { + try { + // Load id tags file + idTags = JSON.parse(fs.readFileSync(file, 'utf8')) as string[]; + } catch (error) { + FileUtils.handleFileException( + file, + FileType.Authorization, + error as NodeJS.ErrnoException, + this.logPrefix(file) + ); + } + } else { + logger.info(`${this.logPrefix(file)} No id tags file given`); + } + return idTags; + } + + private logPrefix = (file: string): string => { + return Utils.logPrefix(` Id tags cache for id tags file '${file}' |`); + }; +} diff --git a/src/charging-station/internal.ts b/src/charging-station/internal.ts index 1f2fef1f..cdeabbaf 100644 --- a/src/charging-station/internal.ts +++ b/src/charging-station/internal.ts @@ -1,4 +1,4 @@ -export * from './AuthorizedTagsCache'; +export * from './IdTagsCache'; export * from './AutomaticTransactionGenerator'; export * from './Bootstrap'; export * from './ChargingStation'; diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 718f2f90..12a64b6c 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -739,12 +739,10 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { let authorized = false; if ( chargingStation.getLocalAuthListEnabled() === true && - chargingStation.hasAuthorizedTags() === true && + chargingStation.hasIdTags() === true && Utils.isNotEmptyString( - chargingStation.authorizedTagsCache - .getAuthorizedTags( - ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo) - ) + chargingStation.idTagsCache + .getIdTags(ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)) ?.find((idTag) => idTag === commandPayload.idTag) ) ) { diff --git a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts index 29ce6ee3..38c22013 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts @@ -441,7 +441,7 @@ export class OCPP16ResponseService extends OCPPResponseService { chargingStation.getConnectorStatus(connectorId)?.transactionRemoteStarted === true && chargingStation.getAuthorizeRemoteTxRequests() === true && chargingStation.getLocalAuthListEnabled() === true && - chargingStation.hasAuthorizedTags() && + chargingStation.hasIdTags() && chargingStation.getConnectorStatus(connectorId)?.idTagLocalAuthorized === false ) { logger.error( diff --git a/src/charging-station/ocpp/OCPPIncomingRequestService.ts b/src/charging-station/ocpp/OCPPIncomingRequestService.ts index bca4809e..32402da5 100644 --- a/src/charging-station/ocpp/OCPPIncomingRequestService.ts +++ b/src/charging-station/ocpp/OCPPIncomingRequestService.ts @@ -90,7 +90,7 @@ export abstract class OCPPIncomingRequestService extends AsyncResource { } protected handleRequestClearCache(chargingStation: ChargingStation): ClearCacheResponse { - chargingStation.authorizedTagsCache.deleteAuthorizedTags( + chargingStation.idTagsCache.deleteIdTags( ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo) ); return OCPPConstants.OCPP_RESPONSE_ACCEPTED; diff --git a/src/types/ChargingStationTemplate.ts b/src/types/ChargingStationTemplate.ts index aa9a1b7a..0cd0e45f 100644 --- a/src/types/ChargingStationTemplate.ts +++ b/src/types/ChargingStationTemplate.ts @@ -67,6 +67,7 @@ export type ChargingStationTemplate = { ocppPersistentConfiguration?: boolean; stationInfoPersistentConfiguration?: boolean; wsOptions?: WsOptions; + // FIXME: rename to idTagFile authorizationFile?: string; baseName: string; nameSuffix?: string; -- 2.34.1