refactor(simulator): cleanup id tags cache namespace
authorJérôme Benoit <jerome.benoit@sap.com>
Sun, 26 Mar 2023 13:08:37 +0000 (15:08 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Sun, 26 Mar 2023 13:08:37 +0000 (15:08 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/AuthorizedTagsCache.ts [deleted file]
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/ChargingStation.ts
src/charging-station/IdTagsCache.ts [new file with mode: 0644]
src/charging-station/internal.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/OCPPIncomingRequestService.ts
src/types/ChargingStationTemplate.ts

diff --git a/src/charging-station/AuthorizedTagsCache.ts b/src/charging-station/AuthorizedTagsCache.ts
deleted file mode 100644 (file)
index edb6953..0000000
+++ /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<string, TagsCacheValueType>;
-  private readonly tagsCachesAddressableIndexes: Map<string, number>;
-
-  private constructor() {
-    this.tagsCaches = new Map<string, TagsCacheValueType>();
-    this.tagsCachesAddressableIndexes = new Map<string, number>();
-  }
-
-  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}' |`);
-  };
-}
index aa2012dfac33eae26a49dc86b688bda0e6d26c28..9728108b0fbb1f4f4676176b0338a5f6f628a2a7 100644 (file)
@@ -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<number, Status>();
     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
index 81170bad83c4b2744711b7bef10809517c64b4b7..cc7177119093e12df8e64d613b0c01e09294950a 100644 (file)
@@ -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<string, CachedRequest>();
     this.messageBuffer = new Set<string>();
     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 (file)
index 0000000..7b49b59
--- /dev/null
@@ -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<string, IdTagsCacheValueType>;
+  private readonly idTagsCachesAddressableIndexes: Map<string, number>;
+
+  private constructor() {
+    this.idTagsCaches = new Map<string, IdTagsCacheValueType>();
+    this.idTagsCachesAddressableIndexes = new Map<string, number>();
+  }
+
+  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}' |`);
+  };
+}
index 1f2fef1f834b1a714891e3e38a9839e5c7f7e974..cdeabbaf96003f8fbbd0476994f74be2b7a7cbb7 100644 (file)
@@ -1,4 +1,4 @@
-export * from './AuthorizedTagsCache';
+export * from './IdTagsCache';
 export * from './AutomaticTransactionGenerator';
 export * from './Bootstrap';
 export * from './ChargingStation';
index 718f2f90319b9bb58bd25e57457be1f77e52185c..12a64b6ccd418498e3dae139cee4f8f1c1a133a6 100644 (file)
@@ -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)
             )
           ) {
index 29ce6ee3360941b0e80dde5b57d0bb2d0dabe83b..38c22013e959faf1c34bac73e68cf70f438d71e4 100644 (file)
@@ -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(
index bca4809e8d38a2af5fc00ae66d619f73d182d742..32402da5f139a4e9945e6f8d6bc45ac0e075b998 100644 (file)
@@ -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;
index aa9a1b7a200f9a32be746236f5b6f0c3d495cda9..0cd0e45f1e49707de532e5adab330ba3878c01f2 100644 (file)
@@ -67,6 +67,7 @@ export type ChargingStationTemplate = {
   ocppPersistentConfiguration?: boolean;
   stationInfoPersistentConfiguration?: boolean;
   wsOptions?: WsOptions;
+  // FIXME: rename to idTagFile
   authorizationFile?: string;
   baseName: string;
   nameSuffix?: string;