From d994d5a9357ce32f5546d23d4cf0b417edf62b82 Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 20 Jun 2024 12:31:17 +0200 Subject: [PATCH 01/94] Updated DLT Gateway code. Related to Issue #165 --- src/dlt/gateway/.gitignore | 90 - src/dlt/gateway/Dockerfile | 41 - src/dlt/gateway/README.md | 134 - .../gateway/automation/DockerFiles/Dockerfile | 27 + .../automation/k8sconfig/configmap.yaml | 17 + .../k8sconfig/deploy_dlt_gateway.sh | 34 + .../automation/k8sconfig/deployment.yaml | 74 + .../k8sconfig/persistent-volume-claim.yaml | 11 + .../k8sconfig/persistent-volume.yaml | 15 + .../k8sconfig/remove_dlt_gateway.sh | 37 + .../gateway/automation/k8sconfig/service.yaml | 14 + .../automation/k8sconfig/simpletest.yaml | 19 + src/dlt/gateway/build.gradle.kts | 147 - src/dlt/gateway/chaincode/adrenalineTopo.go | 186 ++ src/dlt/gateway/chaincode/go.mod | 32 + src/dlt/gateway/chaincode/go.sum | 1240 +++++++++ .../config/ca.org1.example.com-cert.pem | 14 - src/dlt/gateway/config/connection-org1.json | 73 - src/dlt/gateway/dltApp/.gitignore | 2 + src/dlt/gateway/dltApp/README.md | 46 + src/dlt/gateway/dltApp/package-lock.json | 2420 +++++++++++++++++ src/dlt/gateway/dltApp/package.json | 38 + src/dlt/gateway/dltApp/proto/context.proto | 34 + .../gateway/dltApp/proto/dlt_service.proto | 97 + .../dltApp/proto/dlt_service_grpc_pb.js | 181 ++ .../gateway/dltApp/proto/dlt_service_pb.js | 1633 +++++++++++ src/dlt/gateway/dltApp/src/dltGateway.js | 237 ++ src/dlt/gateway/dltApp/src/fabricConnect.ts | 201 ++ src/dlt/gateway/dltApp/src/testEvents.js | 51 + src/dlt/gateway/dltApp/src/testGateway.js | 111 + .../dltApp/tests/dltApp_Test/.gitignore | 1 + .../tests/dltApp_Test/package-lock.json | 2060 ++++++++++++++ .../dltApp/tests/dltApp_Test/package.json | 34 + .../dltApp_Test/src/adrenalineDLT_app.ts | 341 +++ .../dltApp/tests/dltApp_Test/tsconfig.json | 18 + src/dlt/gateway/dltApp/tests/perfTest.js | 105 + src/dlt/gateway/dltApp/tests/rateTest.js | 71 + src/dlt/gateway/dltApp/tests/simpleTest.js | 60 + src/dlt/gateway/dltApp/tsconfig.json | 18 + src/dlt/gateway/gradle.properties | 1 - .../gateway/gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 - src/dlt/gateway/gradlew | 185 -- src/dlt/gateway/gradlew.bat | 89 - src/dlt/gateway/samples/sampleTopo.json | 30 + src/dlt/gateway/samples/topo1.json | 96 + src/dlt/gateway/samples/topo2.json | 210 ++ src/dlt/gateway/samples/topo3.json | 200 ++ src/dlt/gateway/samples/topo4.json | 150 + src/dlt/gateway/samples/updatedTopo.json | 17 + src/dlt/gateway/settings.gradle.kts | 19 - src/dlt/gateway/src/main/kotlin/Main.kt | 161 -- .../src/main/kotlin/fabric/ConnectGateway.kt | 54 - .../src/main/kotlin/fabric/EnrollAdmin.kt | 29 - .../src/main/kotlin/fabric/FabricConnector.kt | 178 -- .../src/main/kotlin/fabric/RegisterUser.kt | 65 - .../src/main/kotlin/grpc/FabricServer.kt | 94 - .../src/main/kotlin/grpc/GrpcHandler.kt | 95 - .../src/main/kotlin/proto/Config.proto | 54 - 59 files changed, 10168 insertions(+), 1528 deletions(-) delete mode 100644 src/dlt/gateway/.gitignore delete mode 100644 src/dlt/gateway/Dockerfile delete mode 100644 src/dlt/gateway/README.md create mode 100644 src/dlt/gateway/automation/DockerFiles/Dockerfile create mode 100644 src/dlt/gateway/automation/k8sconfig/configmap.yaml create mode 100644 src/dlt/gateway/automation/k8sconfig/deploy_dlt_gateway.sh create mode 100644 src/dlt/gateway/automation/k8sconfig/deployment.yaml create mode 100644 src/dlt/gateway/automation/k8sconfig/persistent-volume-claim.yaml create mode 100644 src/dlt/gateway/automation/k8sconfig/persistent-volume.yaml create mode 100644 src/dlt/gateway/automation/k8sconfig/remove_dlt_gateway.sh create mode 100644 src/dlt/gateway/automation/k8sconfig/service.yaml create mode 100644 src/dlt/gateway/automation/k8sconfig/simpletest.yaml delete mode 100644 src/dlt/gateway/build.gradle.kts create mode 100644 src/dlt/gateway/chaincode/adrenalineTopo.go create mode 100644 src/dlt/gateway/chaincode/go.mod create mode 100644 src/dlt/gateway/chaincode/go.sum delete mode 100644 src/dlt/gateway/config/ca.org1.example.com-cert.pem delete mode 100644 src/dlt/gateway/config/connection-org1.json create mode 100644 src/dlt/gateway/dltApp/.gitignore create mode 100644 src/dlt/gateway/dltApp/README.md create mode 100644 src/dlt/gateway/dltApp/package-lock.json create mode 100644 src/dlt/gateway/dltApp/package.json create mode 100644 src/dlt/gateway/dltApp/proto/context.proto create mode 100644 src/dlt/gateway/dltApp/proto/dlt_service.proto create mode 100644 src/dlt/gateway/dltApp/proto/dlt_service_grpc_pb.js create mode 100644 src/dlt/gateway/dltApp/proto/dlt_service_pb.js create mode 100644 src/dlt/gateway/dltApp/src/dltGateway.js create mode 100644 src/dlt/gateway/dltApp/src/fabricConnect.ts create mode 100644 src/dlt/gateway/dltApp/src/testEvents.js create mode 100644 src/dlt/gateway/dltApp/src/testGateway.js create mode 100644 src/dlt/gateway/dltApp/tests/dltApp_Test/.gitignore create mode 100644 src/dlt/gateway/dltApp/tests/dltApp_Test/package-lock.json create mode 100644 src/dlt/gateway/dltApp/tests/dltApp_Test/package.json create mode 100644 src/dlt/gateway/dltApp/tests/dltApp_Test/src/adrenalineDLT_app.ts create mode 100644 src/dlt/gateway/dltApp/tests/dltApp_Test/tsconfig.json create mode 100644 src/dlt/gateway/dltApp/tests/perfTest.js create mode 100644 src/dlt/gateway/dltApp/tests/rateTest.js create mode 100644 src/dlt/gateway/dltApp/tests/simpleTest.js create mode 100644 src/dlt/gateway/dltApp/tsconfig.json delete mode 100644 src/dlt/gateway/gradle.properties delete mode 100644 src/dlt/gateway/gradle/wrapper/gradle-wrapper.jar delete mode 100644 src/dlt/gateway/gradle/wrapper/gradle-wrapper.properties delete mode 100755 src/dlt/gateway/gradlew delete mode 100644 src/dlt/gateway/gradlew.bat create mode 100644 src/dlt/gateway/samples/sampleTopo.json create mode 100644 src/dlt/gateway/samples/topo1.json create mode 100644 src/dlt/gateway/samples/topo2.json create mode 100644 src/dlt/gateway/samples/topo3.json create mode 100644 src/dlt/gateway/samples/topo4.json create mode 100644 src/dlt/gateway/samples/updatedTopo.json delete mode 100644 src/dlt/gateway/settings.gradle.kts delete mode 100644 src/dlt/gateway/src/main/kotlin/Main.kt delete mode 100644 src/dlt/gateway/src/main/kotlin/fabric/ConnectGateway.kt delete mode 100644 src/dlt/gateway/src/main/kotlin/fabric/EnrollAdmin.kt delete mode 100644 src/dlt/gateway/src/main/kotlin/fabric/FabricConnector.kt delete mode 100644 src/dlt/gateway/src/main/kotlin/fabric/RegisterUser.kt delete mode 100644 src/dlt/gateway/src/main/kotlin/grpc/FabricServer.kt delete mode 100644 src/dlt/gateway/src/main/kotlin/grpc/GrpcHandler.kt delete mode 100644 src/dlt/gateway/src/main/kotlin/proto/Config.proto diff --git a/src/dlt/gateway/.gitignore b/src/dlt/gateway/.gitignore deleted file mode 100644 index 9ecdb254c..000000000 --- a/src/dlt/gateway/.gitignore +++ /dev/null @@ -1,90 +0,0 @@ -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -# From https://github.com/github/gitignore/blob/master/Gradle.gitignore -/.gradle/ -/build/ - -# Ignore Gradle GUI config -gradle-app.setting - -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar - -# Cache of project -.gradletasknamecache - -local.properties -wallet*/ \ No newline at end of file diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile deleted file mode 100644 index 5b888b410..000000000 --- a/src/dlt/gateway/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -FROM zenika/kotlin:1.4-jdk12 - -# Make working directory move to it and copy DLT Gateway code -RUN mkdir -p /var/teraflow/dlt/gateway -WORKDIR /var/teraflow/dlt/gateway -COPY src/dlt/gateway/. ./ - -# Make directory for proto files and copy them -RUN mkdir proto -COPY proto/*.proto ./proto/ - -# Build DLT Gateway -RUN ./gradlew build - -EXPOSE 50051 - -# Create entrypoint.sh script -RUN echo "#!/bin/sh" > /entrypoint.sh -RUN echo "echo 195.37.154.24 peer0.org1.example.com >> /etc/hosts" >> /entrypoint.sh -RUN echo "echo 195.37.154.24 peer0.org2.example.com >> /etc/hosts" >> /entrypoint.sh -RUN echo "echo 195.37.154.24 orderer0.example.com >> /etc/hosts" >> /entrypoint.sh -RUN echo "cd /var/teraflow/dlt/gateway" >> /entrypoint.sh -RUN echo "./gradlew runServer" >> /entrypoint.sh -RUN chmod +x /entrypoint.sh - -# Gateway entry point -ENTRYPOINT ["sh", "/entrypoint.sh"] diff --git a/src/dlt/gateway/README.md b/src/dlt/gateway/README.md deleted file mode 100644 index 2cf6cfeb1..000000000 --- a/src/dlt/gateway/README.md +++ /dev/null @@ -1,134 +0,0 @@ -``` - NEC Laboratories Europe GmbH - - PROPRIETARY INFORMATION - - The software and its source code contain valuable trade secrets and - shall be maintained in confidence and treated as confidential - information. The software may only be used for evaluation and/or - testing purposes, unless otherwise explicitly stated in a written - agreement with NEC Laboratories Europe GmbH. - - Any unauthorized publication, transfer to third parties or - duplication of the object or source code - either totally or in - part - is strictly prohibited. - - Copyright (c) 2022 NEC Laboratories Europe GmbH - All Rights Reserved. - - Authors: Konstantin Munichev - - - NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE - WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND - THE ACCOMPANYING DOCUMENTATION. - - NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC - Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR - ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR - LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF - INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, - INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF - OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe - GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - - THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. - ``` - -# DLT module guide - -## General information -The DLT module is used to provide access to the underlying Fabric deployment. It allows clients -to add, retrieve, modify and delete blockchain-backed data, essentially working as a key-value -database. External clients should use gRPC API to communicate with this service, its detailed -description available below. - -## Code structure -The whole DLT module consists of several packages: -- fabric package -- http package -- proto package -- client example - -### Fabric package -The most important class in this package is `FabricConnector`. First, it establishes connection -with the underlying Fabric network using Java Gateway SDK. After that, it could be used as a -CRUD interface. -Other files contain auxiliary code for `FabricConnector` which allows it to register/enroll -users and to obtain smart contract instances. - -### Grpc package -Contains server side gRPC handler. It accepts requests from the outside and performs the -requested operation. For the more detailed description see Proto package description right below. - -### Proto package -The proto package contains `dlt.proto` file which defines gRPC service `DltService` API and messages -it uses. There are 3 main functions: `RecordToDlt` which allows to create/modify/delete data, -`GetFromDlt` which returns already written data and `SubscribeToDlt` which allows clients subscribe -for future create/modify/delete events with provided filters. -Other proto files don't play any significant role and could be safely ignored by end users. - -### Client example -This code is not necessary to the service, but it could be used to test the service. It contains -a sample gRPC client which connects the service and perform all the CRUD operations. - -# Fabric deployment notes - -## General notes -Current Fabric deployment uses Fabric test network with some additional helping scripts on top of it. -To start the network just run the `raft.sh` from `blockchain/scripts` directory. Use `stop.sh` -when you need to stop the network. - -## Server start preparations -To run the server it's necessary to copy certificate file -`fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem` -to the config folder (replacing the existing one). Also, it's necessary to copy `scripts/connection-org1.json` -file (again, replacing the old one). After copying, it must be edited. First, all `localhost` entrances -should be replaced with `teraflow.nlehd.de`. Second, `channel` section at the end of the file should be removed. -This should be done after every restart of the Fabric network. - -## Fabric configuration -Even though a test network is easy to deploy and use it's better to perform a custom configuration -for a production deployment. In practice every participating organization will likely prefer to have -its own Peer/Orderer/CA instances to prevent possible dependency on any other participants. This leads -not only to a better privacy/availability/security in general but also to the more complicated -deployment process as a side effect. Here we provide a very brief description of the most important points. - -### Organizations -Organization represents a network participant, which can be an individual, a large corporation or any other -entity. Each organization has its own CAs, orderers and peers. The recommendation here is to create an -organization entity for every independent participant and then decide how many CAs/peers/orderers does -every organization need and which channels should it has access to based on the exact project's goals. - -### Channels -Each channel represents an independent ledger with its own genesis block. Each transaction is executed -on a specific channel, and it's possible to define which organization has access to a given channel. -As a result channels are a pretty powerful privacy mechanism which allows to limit access to the private -data between organization. - -### Certificate authorities, peers and orderers -Certificate authorities (CA) are used to generate crypto materials for each organization. Two types of CA -exist: one is used to generate the certificates of the admin, the MSP and certificates of non-admin users. -Another type of CA is used to generate TLS certificates. As a result it's preferable to have at least two -CAs for every organization. - -Peers are entities which host ledgers and smart contracts. They communicate with applications and orderers, -receiving chaincode invocations (proposals), invoking chaincode, updating ledger when necessary and -returning result of execution. Peers can handle one or many ledgers, depending on the configuration. It's -very use case specific how many peers are necessary to the exact deployment. - -Orderers are used to execute a consensus in a distributing network making sure that every channel participant -has the same blocks with the same data. The default consensus algorithm is Raft which provides only a crash -fault tolerance. - -### Conclusion -As you can see, configuration procedure for Fabric is pretty tricky and includes quite a lot of entities. -In real world it will very likely involve participants from multiple organizations each of them performing -its own part of configuration. - -As a further reading it's recommended to start with the -[official deployment guide](https://hyperledger-fabric.readthedocs.io/en/release-2.2/deployment_guide_overview.html). -It contains a high level overview of a deployment process as well as links to the detailed descriptions to -CA/Peer/Orderer configuration descriptions. \ No newline at end of file diff --git a/src/dlt/gateway/automation/DockerFiles/Dockerfile b/src/dlt/gateway/automation/DockerFiles/Dockerfile new file mode 100644 index 000000000..dcbd81810 --- /dev/null +++ b/src/dlt/gateway/automation/DockerFiles/Dockerfile @@ -0,0 +1,27 @@ +# Use an official Node.js runtime as a parent image +FROM node:20 + +# Set the working directory in the container +WORKDIR /usr/dltApp + +# Copy package.json and package-lock.json +COPY dltApp/package*.json ./ + +# Copy tsconfig.json +COPY dltApp/tsconfig*.json ./ +# Copy the proto folder +COPY dltApp/proto/ ./proto + +# Copy the src folder +COPY dltApp/src/ ./src + +# Install dependencies +RUN npm install + + + +# Expose the port that the gRPC service runs on +EXPOSE 50051 + +# Command to run the service +CMD ["node", "src/dltGateway.js"] \ No newline at end of file diff --git a/src/dlt/gateway/automation/k8sconfig/configmap.yaml b/src/dlt/gateway/automation/k8sconfig/configmap.yaml new file mode 100644 index 000000000..f8e19b798 --- /dev/null +++ b/src/dlt/gateway/automation/k8sconfig/configmap.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: dlt-config + namespace: dlt +#NEED to find a better way to setup ENV variables for Kubernetes services +##Modify the PEER_ENDPOINT IP according to your deployment +data: + CHANNEL_NAME: "channel1" + CHAINCODE_NAME: "adrenalineDLT" + MSP_ID: "Org1MSP" + PEER_ENDPOINT: "PEER_IP:PORT" #USE THE IP address and Ports of your Fabric deployment peers. + PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" + CRYPTO_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com" + KEY_DIRECTORY_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/keystore" + CERT_DIRECTORY_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/signcerts" + TLS_CERT_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/peers/peer0.org1.adrenaline.com/tls/ca.crt" diff --git a/src/dlt/gateway/automation/k8sconfig/deploy_dlt_gateway.sh b/src/dlt/gateway/automation/k8sconfig/deploy_dlt_gateway.sh new file mode 100644 index 000000000..736d65ee8 --- /dev/null +++ b/src/dlt/gateway/automation/k8sconfig/deploy_dlt_gateway.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Namespace +NAMESPACE="dlt" + +# Configurations +CONFIGMAP="configmap.yaml" +PV="persistent-volume.yaml" +PVC="persistent-volume-claim.yaml" +DEPLOYMENT="deployment.yaml" +SERVICE="service.yaml" + +# Apply Configurations +echo "Applying ConfigMap..." +kubectl apply -f $CONFIGMAP + +echo "Applying PersistentVolume..." +kubectl apply -f $PV + +echo "Applying PersistentVolumeClaim..." +kubectl apply -f $PVC + +echo "Applying Deployment..." +kubectl apply -f $DEPLOYMENT + +echo "Applying Service..." +kubectl apply -f $SERVICE + +# Verify Deployment +echo "Verifying Deployment..." +kubectl get pods -n $NAMESPACE +kubectl get services -n $NAMESPACE + +echo "Deployment Completed." diff --git a/src/dlt/gateway/automation/k8sconfig/deployment.yaml b/src/dlt/gateway/automation/k8sconfig/deployment.yaml new file mode 100644 index 000000000..88ca9a5af --- /dev/null +++ b/src/dlt/gateway/automation/k8sconfig/deployment.yaml @@ -0,0 +1,74 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dlt-gateway + namespace: dlt +spec: + replicas: 3 + selector: + matchLabels: + app: dlt-gateway + template: + metadata: + labels: + app: dlt-gateway + spec: + containers: + - name: dlt-gateway + image: shaifvier/dltgateway:v1.0.0 + ports: + - containerPort: 50051 + volumeMounts: + - mountPath: /test-network + name: dlt-volume + readOnly: true # Mount the volume as read-only + env: + - name: CHANNEL_NAME + valueFrom: + configMapKeyRef: + name: dlt-config + key: CHANNEL_NAME + - name: CHAINCODE_NAME + valueFrom: + configMapKeyRef: + name: dlt-config + key: CHAINCODE_NAME + - name: MSP_ID + valueFrom: + configMapKeyRef: + name: dlt-config + key: MSP_ID + - name: PEER_ENDPOINT + valueFrom: + configMapKeyRef: + name: dlt-config + key: PEER_ENDPOINT + - name: PEER_HOST_ALIAS + valueFrom: + configMapKeyRef: + name: dlt-config + key: PEER_HOST_ALIAS + - name: CRYPTO_PATH + valueFrom: + configMapKeyRef: + name: dlt-config + key: CRYPTO_PATH + - name: KEY_DIRECTORY_PATH + valueFrom: + configMapKeyRef: + name: dlt-config + key: KEY_DIRECTORY_PATH + - name: CERT_DIRECTORY_PATH + valueFrom: + configMapKeyRef: + name: dlt-config + key: CERT_DIRECTORY_PATH + - name: TLS_CERT_PATH + valueFrom: + configMapKeyRef: + name: dlt-config + key: TLS_CERT_PATH + volumes: + - name: dlt-volume + persistentVolumeClaim: + claimName: dlt-pvc diff --git a/src/dlt/gateway/automation/k8sconfig/persistent-volume-claim.yaml b/src/dlt/gateway/automation/k8sconfig/persistent-volume-claim.yaml new file mode 100644 index 000000000..a1e4f477c --- /dev/null +++ b/src/dlt/gateway/automation/k8sconfig/persistent-volume-claim.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: dlt-pvc + namespace: dlt +spec: + accessModes: + - ReadOnlyMany + resources: + requests: + storage: 1Gi diff --git a/src/dlt/gateway/automation/k8sconfig/persistent-volume.yaml b/src/dlt/gateway/automation/k8sconfig/persistent-volume.yaml new file mode 100644 index 000000000..435b02a25 --- /dev/null +++ b/src/dlt/gateway/automation/k8sconfig/persistent-volume.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: dlt-pv +spec: + capacity: + storage: 1Gi + accessModes: + - ReadOnlyMany + persistentVolumeReclaimPolicy: Retain + hostPath: + path: "/home/cttc/test-network" # Update this path to the actual path on the host machine where the Kubernetes Pod will be deployed. + claimRef: + namespace: dlt + name: dlt-pvc \ No newline at end of file diff --git a/src/dlt/gateway/automation/k8sconfig/remove_dlt_gateway.sh b/src/dlt/gateway/automation/k8sconfig/remove_dlt_gateway.sh new file mode 100644 index 000000000..5633789f3 --- /dev/null +++ b/src/dlt/gateway/automation/k8sconfig/remove_dlt_gateway.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Namespace +NAMESPACE="dlt" + +# Configurations +CONFIGMAP="configmap.yaml" +PV="persistent-volume.yaml" +PVC="persistent-volume-claim.yaml" +DEPLOYMENT="deployment.yaml" +SERVICE="service.yaml" + +# Delete Configurations +echo "Deleting Deployment..." +kubectl delete -f $DEPLOYMENT || echo "Deployment not found." + +echo "Deleting Service..." +kubectl delete -f $SERVICE || echo "Service not found." + +echo "Deleting PersistentVolumeClaim..." +kubectl delete -f $PVC || echo "PersistentVolumeClaim not found." + +echo "Deleting PersistentVolume..." +kubectl delete -f $PV || echo "PersistentVolume not found." + +echo "Deleting ConfigMap..." +kubectl delete -f $CONFIGMAP || echo "ConfigMap not found." + +# Verify Deletion +echo "Verifying Deletion..." +kubectl get pods -n $NAMESPACE +kubectl get services -n $NAMESPACE +kubectl get pvc -n $NAMESPACE +kubectl get pv +kubectl get configmap -n $NAMESPACE + +echo "Deletion Completed." diff --git a/src/dlt/gateway/automation/k8sconfig/service.yaml b/src/dlt/gateway/automation/k8sconfig/service.yaml new file mode 100644 index 000000000..2590e99c9 --- /dev/null +++ b/src/dlt/gateway/automation/k8sconfig/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: dlt-gateway + namespace: dlt +spec: + selector: + app: dlt-gateway + ports: + - protocol: TCP + port: 50051 # External port + targetPort: 50051 # Internal port of the service + nodePort: 32001 # A high port number for external access + type: NodePort diff --git a/src/dlt/gateway/automation/k8sconfig/simpletest.yaml b/src/dlt/gateway/automation/k8sconfig/simpletest.yaml new file mode 100644 index 000000000..0926285b8 --- /dev/null +++ b/src/dlt/gateway/automation/k8sconfig/simpletest.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Pod +metadata: + name: dlt-gateway + namespace: dlt +spec: + containers: + - name: test-container + image: busybox + command: ["sh", "-c", "sleep 3600"] # Keep the container running for testing + volumeMounts: + - mountPath: /mnt/test-network + name: dlt-volume + readOnly: true + volumes: + - name: dlt-volume + hostPath: + path: /home/cttc/test-network + type: Directory diff --git a/src/dlt/gateway/build.gradle.kts b/src/dlt/gateway/build.gradle.kts deleted file mode 100644 index b65aff89e..000000000 --- a/src/dlt/gateway/build.gradle.kts +++ /dev/null @@ -1,147 +0,0 @@ -// NEC Laboratories Europe GmbH -// -// PROPRIETARY INFORMATION -// -// The software and its source code contain valuable trade secrets and -// shall be maintained in confidence and treated as confidential -// information. The software may only be used for evaluation and/or -// testing purposes, unless otherwise explicitly stated in a written -// agreement with NEC Laboratories Europe GmbH. -// -// Any unauthorized publication, transfer to third parties or -// duplication of the object or source code - either totally or in -// part - is strictly prohibited. -// -// Copyright (c) 2021 NEC Laboratories Europe GmbH -// All Rights Reserved. -// -// Authors: Konstantin Munichev -// -// -// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE -// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND -// THE ACCOMPANYING DOCUMENTATION. -// -// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC -// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR -// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR -// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF -// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, -// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF -// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe -// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. - -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -import com.google.protobuf.gradle.generateProtoTasks -import com.google.protobuf.gradle.id -import com.google.protobuf.gradle.plugins -import com.google.protobuf.gradle.protobuf -import com.google.protobuf.gradle.protoc - -ext["grpcVersion"] = "1.47.0" -ext["grpcKotlinVersion"] = "1.3.0" // CURRENT_GRPC_KOTLIN_VERSION -ext["protobufVersion"] = "3.20.1" -ext["ktorVersion"] = "1.6.5" - -plugins { - kotlin("jvm") version "1.6.21" - kotlin("plugin.serialization") version "1.4.21" - id("com.google.protobuf") version "0.8.18" - application -} - -group = "eu.neclab" -version = "1.0-SNAPSHOT" - -repositories { - mavenLocal() - google() - mavenCentral() -} - -dependencies { - implementation(kotlin("stdlib-jdk8")) - testImplementation("org.jetbrains.kotlin:kotlin-test:1.6.21") - implementation("javax.annotation:javax.annotation-api:1.3.2") - implementation("io.grpc:grpc-kotlin-stub:1.3.0") - implementation("io.grpc:grpc-protobuf:1.47.0") - implementation("com.google.protobuf:protobuf-kotlin:3.21.1") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3") - implementation("org.hyperledger.fabric:fabric-gateway-java:2.2.5") - implementation("ch.qos.logback:logback-classic:1.2.11") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.3.1") - runtimeOnly("io.grpc:grpc-netty:${rootProject.ext["grpcVersion"]}") -} - -tasks.test { - useJUnitPlatform() -} - -tasks.withType { - kotlinOptions.jvmTarget = "11" -} - -tasks.withType().all { - kotlinOptions { - freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn") - } -} - - -application { - mainClass.set("MainKt") -} - -task("runServer", JavaExec::class) { - main = "grpc.FabricServerKt" - classpath = sourceSets["main"].runtimeClasspath -} - - -sourceSets { - main { - proto { - srcDir("proto") - srcDir("src/main/kotlin/proto") - } - } -} - -sourceSets { - val main by getting { } - main.java.srcDirs("build/generated/source/proto/main/grpc") - main.java.srcDirs("build/generated/source/proto/main/grpckt") - main.java.srcDirs("build/generated/source/proto/main/java") - main.java.srcDirs("build/generated/source/proto/main/kotlin") -} - -protobuf { - protoc { - artifact = "com.google.protobuf:protoc:${rootProject.ext["protobufVersion"]}" - } - plugins { - id("grpc") { - artifact = "io.grpc:protoc-gen-grpc-java:${rootProject.ext["grpcVersion"]}" - } - id("grpckt") { - artifact = "io.grpc:protoc-gen-grpc-kotlin:${rootProject.ext["grpcKotlinVersion"]}:jdk8@jar" - } - } - generateProtoTasks { - all().forEach { - it.plugins { - id("grpc") - id("grpckt") - } - it.builtins { - id("kotlin") - } - } - } -} diff --git a/src/dlt/gateway/chaincode/adrenalineTopo.go b/src/dlt/gateway/chaincode/adrenalineTopo.go new file mode 100644 index 000000000..4052d7c2c --- /dev/null +++ b/src/dlt/gateway/chaincode/adrenalineTopo.go @@ -0,0 +1,186 @@ +package main + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "log" + + "github.com/hyperledger/fabric-contract-api-go/contractapi" +) + +// Protobuf definitions +type Uuid struct { + Uuid string `json:"uuid"` +} + +type DltRecordId struct { + DomainUuid Uuid `json:"domain_uuid"` + Type string `json:"type"` + RecordUuid Uuid `json:"record_uuid"` +} + +type DltRecord struct { + RecordId DltRecordId `json:"record_id"` + DataJson string `json:"data_json"` +} + +type SmartContract struct { + contractapi.Contract +} + +// InitLedger activates the chaincode +func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error { + return nil +} + +func (s *SmartContract) StoreRecord(ctx contractapi.TransactionContextInterface, recordId DltRecordId, dataJson string) error { + + key, err := createHashKey(recordId) + if err != nil { + return fmt.Errorf("failed to create hash key: %v", err) + } + + // Check if the same record does not exist before adding it to the ledger + exists, err := s.RecordExists(ctx, key) + if err == nil && exists != nil { + return fmt.Errorf("the record %s already exists", key) + } + + // Trigger an event if the transaction is successful + storedRecord := DltRecord{ + RecordId: recordId, + DataJson: dataJson, + } + eventJson, err := json.Marshal(storedRecord) + if err != nil { + return fmt.Errorf("failed to marshal stored record: %v", err) + } + ctx.GetStub().SetEvent("StoreRecord", eventJson) + + // Store the record in the ledger + return ctx.GetStub().PutState(key, []byte(dataJson)) +} + +func (s *SmartContract) RetrieveRecord(ctx contractapi.TransactionContextInterface, recordId DltRecordId) (string, error) { + key, err := createHashKey(recordId) + if err != nil { + return "", fmt.Errorf("failed to create hash key: %v", err) + } + + // Get the record from the ledger + dataBytes, err := ctx.GetStub().GetState(key) + if err != nil || dataBytes == nil { + return "", fmt.Errorf("data not found for key %s", key) + } + return string(dataBytes), nil +} + +func (s *SmartContract) UpdateRecord(ctx contractapi.TransactionContextInterface, recordId DltRecordId, dataJson string) error { + key, err := createHashKey(recordId) + if err != nil { + return fmt.Errorf("failed to create hash key: %v", err) + } + + // Check if the record exists before updating it + _, err = s.RecordExists(ctx, key) + if err != nil { + return err + } + + // Trigger an event if the transaction is successful + eventData := DltRecord{RecordId: recordId, DataJson: dataJson} + eventJson, err := json.Marshal(eventData) + if err != nil { + return fmt.Errorf("failed to marshal event data: %v", err) + } + ctx.GetStub().SetEvent("UpdateRecord", eventJson) + + // Update the record in the ledger + return ctx.GetStub().PutState(key, []byte(dataJson)) +} + +func (s *SmartContract) DeleteRecord(ctx contractapi.TransactionContextInterface, recordId DltRecordId) error { + key, err := createHashKey(recordId) + if err != nil { + return fmt.Errorf("failed to create hash key: %v", err) + } + + // Check if the record exists before deleting it + exists, err := s.RecordExists(ctx, key) + if err != nil { + return err + } + + // Trigger an event if the transaction is successful + eventData := DltRecord{RecordId: recordId, DataJson: string(exists)} + eventJson, err := json.Marshal(eventData) + if err != nil { + return fmt.Errorf("failed to marshal event data: %v", err) + } + ctx.GetStub().SetEvent("DeleteRecord", eventJson) + + // Delete the record from the ledger + return ctx.GetStub().DelState(key) +} + +func (s *SmartContract) RecordExists(ctx contractapi.TransactionContextInterface, key string) ([]byte, error) { + jsonData, err := ctx.GetStub().GetState(key) + if err != nil { + return nil, fmt.Errorf("failed to read from world state: %v", err) + } + if jsonData == nil { + return nil, fmt.Errorf("the record %s does not exist", key) + } + return jsonData, nil +} + +func (s *SmartContract) GetAllRecords(ctx contractapi.TransactionContextInterface) ([]map[string]interface{}, error) { + // Range query with empty string for startKey and endKey does an + // open-ended query of all records in the chaincode namespace. + resultsIterator, err := ctx.GetStub().GetStateByRange("", "") + if err != nil { + return nil, err + } + defer resultsIterator.Close() + + var records []map[string]interface{} + for resultsIterator.HasNext() { + queryResponse, err := resultsIterator.Next() + if err != nil { + return nil, err + } + + var generic map[string]interface{} + if err := json.Unmarshal(queryResponse.Value, &generic); err != nil { + return nil, fmt.Errorf("invalid JSON data: %v", err) + } + + records = append(records, generic) + } + + return records, nil +} + +func createHashKey(recordId DltRecordId) (string, error) { + recordIdJson, err := json.Marshal(recordId) + if err != nil { + return "", fmt.Errorf("failed to marshal record ID: %v", err) + } + + hash := sha256.New() + hash.Write(recordIdJson) + return hex.EncodeToString(hash.Sum(nil)), nil +} + +func main() { + recordChaincode, err := contractapi.NewChaincode(&SmartContract{}) + if err != nil { + log.Panicf("Error creating chaincode: %v", err) + } + + if err := recordChaincode.Start(); err != nil { + log.Panicf("Error starting chaincode: %v", err) + } +} diff --git a/src/dlt/gateway/chaincode/go.mod b/src/dlt/gateway/chaincode/go.mod new file mode 100644 index 000000000..d6c9e7a40 --- /dev/null +++ b/src/dlt/gateway/chaincode/go.mod @@ -0,0 +1,32 @@ +module adrenalineTopo + +go 1.17 + +require github.com/hyperledger/fabric-contract-api-go v1.2.1 + +require ( + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/spec v0.20.8 // indirect + github.com/go-openapi/swag v0.21.1 // indirect + github.com/gobuffalo/envy v1.10.1 // indirect + github.com/gobuffalo/packd v1.0.1 // indirect + github.com/gobuffalo/packr v1.30.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/hyperledger/fabric-chaincode-go v0.0.0-20230228194215-b84622ba6a7a // indirect + github.com/hyperledger/fabric-protos-go v0.3.0 // indirect + github.com/joho/godotenv v1.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/rogpeppe/go-internal v1.8.1 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/grpc v1.53.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/src/dlt/gateway/chaincode/go.sum b/src/dlt/gateway/chaincode/go.sum new file mode 100644 index 000000000..8bb9581b5 --- /dev/null +++ b/src/dlt/gateway/chaincode/go.sum @@ -0,0 +1,1240 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw= +github.com/cucumber/godog v0.12.6/go.mod h1:Y02TTpimPXDb70PnG6M3zpODXm1+bjCsuZzcW76xAww= +github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= +github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU= +github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.10.1 h1:ppDLoXv2feQ5nus4IcgtyMdHQkKng2lhJCIm33cblM0= +github.com/gobuffalo/envy v1.10.1/go.mod h1:AWx4++KnNOW3JOeEvhSaq+mvgAvnMYOY1XSIin4Mago= +github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= +github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= +github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= +github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= +github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.3.2/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= +github.com/hashicorp/go-memdb v1.3.3/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20230228194215-b84622ba6a7a h1:HwSCxEeiBthwcazcAykGATQ36oG9M+HEQvGLvB7aLvA= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20230228194215-b84622ba6a7a/go.mod h1:TDSu9gxURldEnaGSFbH1eMlfSQBWQcMQfnDBcpQv5lU= +github.com/hyperledger/fabric-contract-api-go v1.2.1 h1:Ww9cKH/qHl5s6WqF+Ts5ju5eaBxC/awB/BJE+rOsEkM= +github.com/hyperledger/fabric-contract-api-go v1.2.1/go.mod h1:BhWve0gz1iH+Xc+cO3rmeIZI7YaTWOQodka9CgeUOgo= +github.com/hyperledger/fabric-protos-go v0.3.0 h1:MXxy44WTMENOh5TI8+PCK2x6pMj47Go2vFRKDHB2PZs= +github.com/hyperledger/fabric-protos-go v0.3.0/go.mod h1:WWnyWP40P2roPmmvxsUXSvVI/CF6vwY1K1UFidnKBys= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/src/dlt/gateway/config/ca.org1.example.com-cert.pem b/src/dlt/gateway/config/ca.org1.example.com-cert.pem deleted file mode 100644 index d7fdf63cc..000000000 --- a/src/dlt/gateway/config/ca.org1.example.com-cert.pem +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICJzCCAc2gAwIBAgIUb5gDMfVeVdQjFkK3uC8LtlogN+gwCgYIKoZIzj0EAwIw -cDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQH -EwZEdXJoYW0xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh -Lm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwOTI3MDgzMDAwWhcNMzcwOTIzMDgzMDAw -WjBwMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExDzANBgNV -BAcTBkR1cmhhbTEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMT -Y2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDC3 -spCTT3pjfFXxkX/SFuBgWRiceR8rSoCNQOnIPeNGZK8xl2Zr7VuY06gqy9c+ecSU -PUWaXiCQxiLgZuS6TOWjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG -AQH/AgEBMB0GA1UdDgQWBBRFWSc7GZqcJJyJjXSEspzgAYInGzAKBggqhkjOPQQD -AgNIADBFAiEAodqc+adkiMuU6iv1IF8uJ/nMQbvMGoP3pb2827QzDosCICOw6W+y -uH03H3RO6KhOcS1ZzPjspyjrcC+dwzYX4DpW ------END CERTIFICATE----- diff --git a/src/dlt/gateway/config/connection-org1.json b/src/dlt/gateway/config/connection-org1.json deleted file mode 100644 index 6f6f3f08d..000000000 --- a/src/dlt/gateway/config/connection-org1.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "name": "test-network-org1", - "version": "1.0.0", - "client": { - "organization": "Org1", - "connection": { - "timeout": { - "peer": { - "endorser": "300" - } - } - } - }, - "organizations": { - "Org1": { - "mspid": "Org1MSP", - "peers": [ - "peer0.org1.example.com" - ], - "certificateAuthorities": [ - "ca.org1.example.com" - ] - } - }, - "peers": { - "peer0.org1.example.com": { - "url": "grpcs://teraflow.nlehd.de:7051", - "tlsCACerts": { - "pem": "-----BEGIN CERTIFICATE-----\nMIICJzCCAc2gAwIBAgIUb5gDMfVeVdQjFkK3uC8LtlogN+gwCgYIKoZIzj0EAwIw\ncDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQH\nEwZEdXJoYW0xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh\nLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwOTI3MDgzMDAwWhcNMzcwOTIzMDgzMDAw\nWjBwMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExDzANBgNV\nBAcTBkR1cmhhbTEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMT\nY2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDC3\nspCTT3pjfFXxkX/SFuBgWRiceR8rSoCNQOnIPeNGZK8xl2Zr7VuY06gqy9c+ecSU\nPUWaXiCQxiLgZuS6TOWjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\nAQH/AgEBMB0GA1UdDgQWBBRFWSc7GZqcJJyJjXSEspzgAYInGzAKBggqhkjOPQQD\nAgNIADBFAiEAodqc+adkiMuU6iv1IF8uJ/nMQbvMGoP3pb2827QzDosCICOw6W+y\nuH03H3RO6KhOcS1ZzPjspyjrcC+dwzYX4DpW\n-----END CERTIFICATE-----\n" - }, - "grpcOptions": { - "ssl-target-name-override": "peer0.org1.example.com", - "hostnameOverride": "peer0.org1.example.com" - } - }, - "peer0.org2.example.com": { - "url": "grpcs://teraflow.nlehd.de:9051", - "tlsCACerts": { - "pem": "-----BEGIN CERTIFICATE-----\nMIICHjCCAcWgAwIBAgIUL48scgv9ItATkBjSNhzYDjLUDsAwCgYIKoZIzj0EAwIw\nbDELMAkGA1UEBhMCVUsxEjAQBgNVBAgTCUhhbXBzaGlyZTEQMA4GA1UEBxMHSHVy\nc2xleTEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eub3Jn\nMi5leGFtcGxlLmNvbTAeFw0yMjA5MjcwODMwMDBaFw0zNzA5MjMwODMwMDBaMGwx\nCzAJBgNVBAYTAlVLMRIwEAYDVQQIEwlIYW1wc2hpcmUxEDAOBgNVBAcTB0h1cnNs\nZXkxGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2NhLm9yZzIu\nZXhhbXBsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ5qz8FfrEQ5S08\nr/avPyTrF2grXj5L4DnbvF4YEZ5Usnbm8Svovu7PO8uiVcwT5vrt6ssOdpBFZYu3\nNndpojnYo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAd\nBgNVHQ4EFgQUYcp7axYV9AaIptYQqhiCL0VDmXQwCgYIKoZIzj0EAwIDRwAwRAIg\nWT1V8/6flUPNcBkmbtEEKf83k7+6sR9k1a2wtVeJFnQCIE0ZSIL3k0dKQydQBpiz\nPcZZUULvQivcMlIsw5+mjIGc\n-----END CERTIFICATE-----\n" - }, - "grpcOptions": { - "ssl-target-name-override": "peer0.org2.example.com", - "hostnameOverride": "peer0.org2.example.com" - } - } - }, - "certificateAuthorities": { - "ca.org1.example.com": { - "url": "https://teraflow.nlehd.de:7054", - "caName": "ca-org1", - "tlsCACerts": { - "pem": [ - "-----BEGIN CERTIFICATE-----\nMIICJzCCAc2gAwIBAgIUb5gDMfVeVdQjFkK3uC8LtlogN+gwCgYIKoZIzj0EAwIw\ncDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQH\nEwZEdXJoYW0xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh\nLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwOTI3MDgzMDAwWhcNMzcwOTIzMDgzMDAw\nWjBwMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExDzANBgNV\nBAcTBkR1cmhhbTEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMT\nY2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDC3\nspCTT3pjfFXxkX/SFuBgWRiceR8rSoCNQOnIPeNGZK8xl2Zr7VuY06gqy9c+ecSU\nPUWaXiCQxiLgZuS6TOWjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\nAQH/AgEBMB0GA1UdDgQWBBRFWSc7GZqcJJyJjXSEspzgAYInGzAKBggqhkjOPQQD\nAgNIADBFAiEAodqc+adkiMuU6iv1IF8uJ/nMQbvMGoP3pb2827QzDosCICOw6W+y\nuH03H3RO6KhOcS1ZzPjspyjrcC+dwzYX4DpW\n-----END CERTIFICATE-----\n" - ] - }, - "httpOptions": { - "verify": false - } - } - }, - "orderers": { - "orderer0.example.com": { - "url": "grpcs://teraflow.nlehd.de:7050", - "tlsCACerts": { - "pem": "-----BEGIN CERTIFICATE-----\nMIICCzCCAbGgAwIBAgIUdZQo3q4OqyxIkidmAV4QkewCylIwCgYIKoZIzj0EAwIw\nYjELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQHEwhOZXcg\nWW9yazEUMBIGA1UEChMLZXhhbXBsZS5jb20xFzAVBgNVBAMTDmNhLmV4YW1wbGUu\nY29tMB4XDTIyMDkyNzA4MzAwMFoXDTM3MDkyMzA4MzAwMFowYjELMAkGA1UEBhMC\nVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQHEwhOZXcgWW9yazEUMBIGA1UE\nChMLZXhhbXBsZS5jb20xFzAVBgNVBAMTDmNhLmV4YW1wbGUuY29tMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAERR0UzsHSFoyON+9Noxmk1IhnTvSdLWGgEpEwrqVr\n5DwitkeJwRWq134JBTmXuZzsUG87oN6Hr94XAEe4j9Zq8qNFMEMwDgYDVR0PAQH/\nBAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFN8XsELp/X0akrlJ\nY3/BWo2jZS3cMAoGCCqGSM49BAMCA0gAMEUCIQCZYYXW/0h3Kq4BmROpOHfrondg\nopf5LndeujYlH3i8tQIgCtpTQiDXZd+IAUduRmn7a46CwJSbjYbXFVX5vumIbE4=\n-----END CERTIFICATE-----\n" - }, - "grpcOptions": { - "ssl-target-name-override": "orderer0.example.com", - "hostnameOverride": "orderer0.example.com" - } - } - } -} diff --git a/src/dlt/gateway/dltApp/.gitignore b/src/dlt/gateway/dltApp/.gitignore new file mode 100644 index 000000000..9e21c69ae --- /dev/null +++ b/src/dlt/gateway/dltApp/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +operation_times.txt \ No newline at end of file diff --git a/src/dlt/gateway/dltApp/README.md b/src/dlt/gateway/dltApp/README.md new file mode 100644 index 000000000..feee576c1 --- /dev/null +++ b/src/dlt/gateway/dltApp/README.md @@ -0,0 +1,46 @@ +# ADRENALINE DLT App + +
+ DLT_APP for chaincode tests +
DLT_APP for chaincode tests.
+
+ +## Description + +The DLT app consists of a **fabricConnect.ts** TypeScript file which contains the logic for Identification management (Certificates required for the MSP), connection management to the blockchain, and finally it exposes a contract object with all the required information for interacting with the chaincode. The compiled **fabricConnect.ts** logic can be imported into a **dltGateway.js** or other testing code inside the [/tests](./tests/) folder. + +## Requisites + +NodeJS + +## Running the App + +Install the dependencies and compile the sourcecode. + +```bash +npm install + +``` + +Run the Gateway application + +```bash +node .\src\dltGateway.js + +``` + +In another terminal run the test client application. + +```bash +node .\src\testGateway.js + +``` + +The purpose of the dltGateway is to expose the chaincode operations to gRPC connections for integration with the ADRENALINE testbed modules. + + +## Performance Test + diff --git a/src/dlt/gateway/dltApp/package-lock.json b/src/dlt/gateway/dltApp/package-lock.json new file mode 100644 index 000000000..9ab326263 --- /dev/null +++ b/src/dlt/gateway/dltApp/package-lock.json @@ -0,0 +1,2420 @@ +{ + "name": "adrenalineDLT_APP", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "adrenalineDLT_APP", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.10.8", + "@grpc/proto-loader": "^0.7.13", + "@hyperledger/fabric-gateway": "~1.4.0", + "dotenv": "^16.4.5", + "grpc-tools": "^1.12.4", + "protobufjs": "^7.3.0", + "uuid": "^9.0.1" + }, + "devDependencies": { + "@tsconfig/node18": "^18.2.2", + "@types/node": "^18.18.6", + "@typescript-eslint/eslint-plugin": "^6.9.0", + "@typescript-eslint/parser": "^6.9.0", + "eslint": "^8.52.0", + "typescript": "~5.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.8.tgz", + "integrity": "sha512-vYVqYzHicDqyKB+NQhAc54I1QWCBLCrYG6unqOIcBTHx+7x8C9lcoLj3KVJXs2VB4lUbpWY+Kk9NipcbXYWmvg==", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@hyperledger/fabric-gateway": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@hyperledger/fabric-gateway/-/fabric-gateway-1.4.0.tgz", + "integrity": "sha512-dJ0eJdGBo8wtZ/oR5mADHnllp+pSuVOI7uq5fRFf0NTVk1SzlX42Q3kt4j53bJQaxd21TMvofgXNO+BCgJcB/A==", + "dependencies": { + "@grpc/grpc-js": "^1.9.0", + "@hyperledger/fabric-protos": "^0.2.0", + "asn1.js": "^5.4.1", + "bn.js": "^5.2.1", + "elliptic": "^6.5.4", + "google-protobuf": "^3.21.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "optionalDependencies": { + "pkcs11js": "^1.3.0" + } + }, + "node_modules/@hyperledger/fabric-protos": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@hyperledger/fabric-protos/-/fabric-protos-0.2.1.tgz", + "integrity": "sha512-qjm0vIQIfCall804tWDeA8p/mUfu14sl5Sj+PbOn2yDKJq+7ThoIhNsLAqf+BCxUfqsoqQq6AojhqQeTFyOOqg==", + "dependencies": { + "@grpc/grpc-js": "^1.9.0", + "google-protobuf": "^3.21.0" + }, + "engines": { + "node": ">=14.15.0" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@tsconfig/node18": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.4.tgz", + "integrity": "sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz", + "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/elliptic": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", + "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-protobuf": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz", + "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/grpc-tools": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/grpc-tools/-/grpc-tools-1.12.4.tgz", + "integrity": "sha512-5+mLAJJma3BjnW/KQp6JBjUMgvu7Mu3dBvBPd1dcbNIb+qiR0817zDpgPjS7gRb+l/8EVNIa3cB02xI9JLToKg==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.5" + }, + "bin": { + "grpc_tools_node_protoc": "bin/protoc.js", + "grpc_tools_node_protoc_plugin": "bin/protoc_plugin.js" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nan": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", + "optional": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkcs11js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkcs11js/-/pkcs11js-1.3.1.tgz", + "integrity": "sha512-eo7fTeQwYGzX1pFmRaf4ji/WcDW2XKpwqylOwzutsjNWECv6G9PzDHj3Yj5dX9EW/fydMnJG8xvWj/btnQT9TA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.15.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/PeculiarVentures" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/protobufjs": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.0.tgz", + "integrity": "sha512-YWD03n3shzV9ImZRX3ccbjqLxj7NokGN0V/ESiBV5xWqrommYHYiihuIyavq03pWSGqlyvYUFmfoMKd+1rPA/g==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.1.tgz", + "integrity": "sha512-f/vbBsu+fOiYt+lmwZV0rVwJScl46HppnOA1ZvIuBWKOTlllpyJ3bfVax76/OrhCH38dyxoDIA8K7uB963IYgA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/src/dlt/gateway/dltApp/package.json b/src/dlt/gateway/dltApp/package.json new file mode 100644 index 000000000..58ec22dea --- /dev/null +++ b/src/dlt/gateway/dltApp/package.json @@ -0,0 +1,38 @@ +{ + "name": "adrenalineDLT_APP", + "version": "1.0.0", + "description": "A DLT application that record and manages topology data as JSON implemented in typeScript using fabric-gateway", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "engines": { + "node": ">=18" + }, + "scripts": { + "build": "tsc", + "build:watch": "tsc -w", + "lint": "eslint . --ext .ts", + "prepare": "npm run build", + "pretest": "npm run lint", + "start": "node dist/adrenalineDLT_app.js" + }, + "engineStrict": true, + "author": "CTTC-Javier", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.10.8", + "@grpc/proto-loader": "^0.7.13", + "@hyperledger/fabric-gateway": "~1.4.0", + "dotenv": "^16.4.5", + "grpc-tools": "^1.12.4", + "protobufjs": "^7.3.0", + "uuid": "^9.0.1" + }, + "devDependencies": { + "@tsconfig/node18": "^18.2.2", + "@types/node": "^18.18.6", + "@typescript-eslint/eslint-plugin": "^6.9.0", + "@typescript-eslint/parser": "^6.9.0", + "eslint": "^8.52.0", + "typescript": "~5.2.2" + } +} diff --git a/src/dlt/gateway/dltApp/proto/context.proto b/src/dlt/gateway/dltApp/proto/context.proto new file mode 100644 index 000000000..88ec0605d --- /dev/null +++ b/src/dlt/gateway/dltApp/proto/context.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; +package context; + +message Empty {} + +message Uuid { + string uuid = 1; +} + +enum EventTypeEnum { + EVENTTYPE_UNDEFINED = 0; + EVENTTYPE_CREATE = 1; + EVENTTYPE_UPDATE = 2; + EVENTTYPE_REMOVE = 3; +} + +message Timestamp { + double timestamp = 1; +} + +message Event { + Timestamp timestamp = 1; + EventTypeEnum event_type = 2; +} + +message TeraFlowController { + ContextId context_id = 1; + string ip_address = 2; + uint32 port = 3; +} + +message ContextId { + Uuid context_uuid = 1; +} diff --git a/src/dlt/gateway/dltApp/proto/dlt_service.proto b/src/dlt/gateway/dltApp/proto/dlt_service.proto new file mode 100644 index 000000000..9f6da08f5 --- /dev/null +++ b/src/dlt/gateway/dltApp/proto/dlt_service.proto @@ -0,0 +1,97 @@ +// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; +package dlt; + +import "context.proto"; + +service DltGatewayService { + rpc RecordToDlt (DltRecord ) returns ( DltRecordStatus ) {} + rpc GetFromDlt (DltRecordId ) returns ( DltRecord ) {} + rpc SubscribeToDlt(DltRecordSubscription ) returns (stream DltRecordEvent ) {} + rpc GetDltStatus (context.TeraFlowController) returns ( DltPeerStatus ) {} // NEC is checking if it is possible + rpc GetDltPeers (context.Empty ) returns ( DltPeerStatusList) {} // NEC is checking if it is possible +} + +enum DltRecordTypeEnum { + DLTRECORDTYPE_UNDEFINED = 0; + DLTRECORDTYPE_CONTEXT = 1; + DLTRECORDTYPE_TOPOLOGY = 2; + DLTRECORDTYPE_DEVICE = 3; + DLTRECORDTYPE_LINK = 4; + DLTRECORDTYPE_SERVICE = 5; + DLTRECORDTYPE_SLICE = 6; +} + +enum DltRecordOperationEnum { + DLTRECORDOPERATION_UNDEFINED = 0; + DLTRECORDOPERATION_ADD = 1; + DLTRECORDOPERATION_UPDATE = 2; + DLTRECORDOPERATION_DELETE = 3; +} + +enum DltRecordStatusEnum { + DLTRECORDSTATUS_UNDEFINED = 0; + DLTRECORDSTATUS_SUCCEEDED = 1; + DLTRECORDSTATUS_FAILED = 2; +} + +enum DltStatusEnum { + DLTSTATUS_UNDEFINED = 0; + DLTSTATUS_NOTAVAILABLE = 1; + DLTSTATUS_INITIALIZED = 2; + DLTSTATUS_AVAILABLE = 3; + DLTSTATUS_DEINIT = 4; +} + +message DltRecordId { + context.Uuid domain_uuid = 1; // unique identifier of domain owning the record + DltRecordTypeEnum type = 2; // type of record + context.Uuid record_uuid = 3; // unique identifier of the record within the domain context_uuid/topology_uuid +} + +message DltRecord { + DltRecordId record_id = 1; // record identifier + DltRecordOperationEnum operation = 2; // operation to be performed over the record + string data_json = 3; // record content: JSON-encoded record content +} + +message DltRecordSubscription { + // retrieved events have to match ALL conditions. + // i.e., type in types requested, AND operation in operations requested + // TODO: consider adding a more sophisticated filtering + repeated DltRecordTypeEnum type = 1; // selected event types, empty=all + repeated DltRecordOperationEnum operation = 2; // selected event operations, empty=all +} + +message DltRecordEvent { + context.Event event = 1; // common event data (timestamp & event_type) + DltRecordId record_id = 2; // record identifier associated with this event +} + +message DltRecordStatus { + DltRecordId record_id = 1; // identifier of the associated record + DltRecordStatusEnum status = 2; // status of the record + string error_message = 3; // error message in case of failure, empty otherwise +} + +message DltPeerStatus { + context.TeraFlowController controller = 1; // Identifier of the TeraFlow controller instance + DltStatusEnum status = 2; // Status of the TeraFlow controller instance +} + +message DltPeerStatusList { + repeated DltPeerStatus peers = 1; // List of peers and their status +} diff --git a/src/dlt/gateway/dltApp/proto/dlt_service_grpc_pb.js b/src/dlt/gateway/dltApp/proto/dlt_service_grpc_pb.js new file mode 100644 index 000000000..85af635c7 --- /dev/null +++ b/src/dlt/gateway/dltApp/proto/dlt_service_grpc_pb.js @@ -0,0 +1,181 @@ +// GENERATED CODE -- DO NOT EDIT! + +// Original file comments: +// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +'use strict'; +var grpc = require('grpc'); +var dlt_service_pb = require('./dlt_service_pb.js'); +var context_pb = require('./context_pb.js'); + +function serialize_context_Empty(arg) { + if (!(arg instanceof context_pb.Empty)) { + throw new Error('Expected argument of type context.Empty'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_context_Empty(buffer_arg) { + return context_pb.Empty.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_context_TeraFlowController(arg) { + if (!(arg instanceof context_pb.TeraFlowController)) { + throw new Error('Expected argument of type context.TeraFlowController'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_context_TeraFlowController(buffer_arg) { + return context_pb.TeraFlowController.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_dlt_DltPeerStatus(arg) { + if (!(arg instanceof dlt_service_pb.DltPeerStatus)) { + throw new Error('Expected argument of type dlt.DltPeerStatus'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_dlt_DltPeerStatus(buffer_arg) { + return dlt_service_pb.DltPeerStatus.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_dlt_DltPeerStatusList(arg) { + if (!(arg instanceof dlt_service_pb.DltPeerStatusList)) { + throw new Error('Expected argument of type dlt.DltPeerStatusList'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_dlt_DltPeerStatusList(buffer_arg) { + return dlt_service_pb.DltPeerStatusList.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_dlt_DltRecord(arg) { + if (!(arg instanceof dlt_service_pb.DltRecord)) { + throw new Error('Expected argument of type dlt.DltRecord'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_dlt_DltRecord(buffer_arg) { + return dlt_service_pb.DltRecord.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_dlt_DltRecordEvent(arg) { + if (!(arg instanceof dlt_service_pb.DltRecordEvent)) { + throw new Error('Expected argument of type dlt.DltRecordEvent'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_dlt_DltRecordEvent(buffer_arg) { + return dlt_service_pb.DltRecordEvent.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_dlt_DltRecordId(arg) { + if (!(arg instanceof dlt_service_pb.DltRecordId)) { + throw new Error('Expected argument of type dlt.DltRecordId'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_dlt_DltRecordId(buffer_arg) { + return dlt_service_pb.DltRecordId.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_dlt_DltRecordStatus(arg) { + if (!(arg instanceof dlt_service_pb.DltRecordStatus)) { + throw new Error('Expected argument of type dlt.DltRecordStatus'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_dlt_DltRecordStatus(buffer_arg) { + return dlt_service_pb.DltRecordStatus.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_dlt_DltRecordSubscription(arg) { + if (!(arg instanceof dlt_service_pb.DltRecordSubscription)) { + throw new Error('Expected argument of type dlt.DltRecordSubscription'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_dlt_DltRecordSubscription(buffer_arg) { + return dlt_service_pb.DltRecordSubscription.deserializeBinary(new Uint8Array(buffer_arg)); +} + + +var DltGatewayServiceService = exports.DltGatewayServiceService = { + recordToDlt: { + path: '/dlt.DltGatewayService/RecordToDlt', + requestStream: false, + responseStream: false, + requestType: dlt_service_pb.DltRecord, + responseType: dlt_service_pb.DltRecordStatus, + requestSerialize: serialize_dlt_DltRecord, + requestDeserialize: deserialize_dlt_DltRecord, + responseSerialize: serialize_dlt_DltRecordStatus, + responseDeserialize: deserialize_dlt_DltRecordStatus, + }, + getFromDlt: { + path: '/dlt.DltGatewayService/GetFromDlt', + requestStream: false, + responseStream: false, + requestType: dlt_service_pb.DltRecordId, + responseType: dlt_service_pb.DltRecord, + requestSerialize: serialize_dlt_DltRecordId, + requestDeserialize: deserialize_dlt_DltRecordId, + responseSerialize: serialize_dlt_DltRecord, + responseDeserialize: deserialize_dlt_DltRecord, + }, + subscribeToDlt: { + path: '/dlt.DltGatewayService/SubscribeToDlt', + requestStream: false, + responseStream: true, + requestType: dlt_service_pb.DltRecordSubscription, + responseType: dlt_service_pb.DltRecordEvent, + requestSerialize: serialize_dlt_DltRecordSubscription, + requestDeserialize: deserialize_dlt_DltRecordSubscription, + responseSerialize: serialize_dlt_DltRecordEvent, + responseDeserialize: deserialize_dlt_DltRecordEvent, + }, + getDltStatus: { + path: '/dlt.DltGatewayService/GetDltStatus', + requestStream: false, + responseStream: false, + requestType: context_pb.TeraFlowController, + responseType: dlt_service_pb.DltPeerStatus, + requestSerialize: serialize_context_TeraFlowController, + requestDeserialize: deserialize_context_TeraFlowController, + responseSerialize: serialize_dlt_DltPeerStatus, + responseDeserialize: deserialize_dlt_DltPeerStatus, + }, + getDltPeers: { + path: '/dlt.DltGatewayService/GetDltPeers', + requestStream: false, + responseStream: false, + requestType: context_pb.Empty, + responseType: dlt_service_pb.DltPeerStatusList, + requestSerialize: serialize_context_Empty, + requestDeserialize: deserialize_context_Empty, + responseSerialize: serialize_dlt_DltPeerStatusList, + responseDeserialize: deserialize_dlt_DltPeerStatusList, + }, +}; + +exports.DltGatewayServiceClient = grpc.makeGenericClientConstructor(DltGatewayServiceService); diff --git a/src/dlt/gateway/dltApp/proto/dlt_service_pb.js b/src/dlt/gateway/dltApp/proto/dlt_service_pb.js new file mode 100644 index 000000000..59782ff7a --- /dev/null +++ b/src/dlt/gateway/dltApp/proto/dlt_service_pb.js @@ -0,0 +1,1633 @@ +// source: dlt_service.proto +/** + * @fileoverview + * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. + * @suppress {messageConventions} JS Compiler reports an error if a variable or + * field starts with 'MSG_' and isn't a translatable message. + * @public + */ +// GENERATED CODE -- DO NOT EDIT! +/* eslint-disable */ +// @ts-nocheck + +var jspb = require('google-protobuf'); +var goog = jspb; +var global = (function() { + if (this) { return this; } + if (typeof window !== 'undefined') { return window; } + if (typeof global !== 'undefined') { return global; } + if (typeof self !== 'undefined') { return self; } + return Function('return this')(); +}.call(null)); + +var context_pb = require('./context_pb.js'); +goog.object.extend(proto, context_pb); +goog.exportSymbol('proto.dlt.DltPeerStatus', null, global); +goog.exportSymbol('proto.dlt.DltPeerStatusList', null, global); +goog.exportSymbol('proto.dlt.DltRecord', null, global); +goog.exportSymbol('proto.dlt.DltRecordEvent', null, global); +goog.exportSymbol('proto.dlt.DltRecordId', null, global); +goog.exportSymbol('proto.dlt.DltRecordOperationEnum', null, global); +goog.exportSymbol('proto.dlt.DltRecordStatus', null, global); +goog.exportSymbol('proto.dlt.DltRecordStatusEnum', null, global); +goog.exportSymbol('proto.dlt.DltRecordSubscription', null, global); +goog.exportSymbol('proto.dlt.DltRecordTypeEnum', null, global); +goog.exportSymbol('proto.dlt.DltStatusEnum', null, global); +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.dlt.DltRecordId = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.dlt.DltRecordId, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.dlt.DltRecordId.displayName = 'proto.dlt.DltRecordId'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.dlt.DltRecord = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.dlt.DltRecord, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.dlt.DltRecord.displayName = 'proto.dlt.DltRecord'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.dlt.DltRecordSubscription = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.dlt.DltRecordSubscription.repeatedFields_, null); +}; +goog.inherits(proto.dlt.DltRecordSubscription, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.dlt.DltRecordSubscription.displayName = 'proto.dlt.DltRecordSubscription'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.dlt.DltRecordEvent = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.dlt.DltRecordEvent, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.dlt.DltRecordEvent.displayName = 'proto.dlt.DltRecordEvent'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.dlt.DltRecordStatus = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.dlt.DltRecordStatus, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.dlt.DltRecordStatus.displayName = 'proto.dlt.DltRecordStatus'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.dlt.DltPeerStatus = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.dlt.DltPeerStatus, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.dlt.DltPeerStatus.displayName = 'proto.dlt.DltPeerStatus'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.dlt.DltPeerStatusList = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.dlt.DltPeerStatusList.repeatedFields_, null); +}; +goog.inherits(proto.dlt.DltPeerStatusList, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.dlt.DltPeerStatusList.displayName = 'proto.dlt.DltPeerStatusList'; +} + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.dlt.DltRecordId.prototype.toObject = function(opt_includeInstance) { + return proto.dlt.DltRecordId.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.dlt.DltRecordId} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltRecordId.toObject = function(includeInstance, msg) { + var f, obj = { + domainUuid: (f = msg.getDomainUuid()) && context_pb.Uuid.toObject(includeInstance, f), + type: jspb.Message.getFieldWithDefault(msg, 2, 0), + recordUuid: (f = msg.getRecordUuid()) && context_pb.Uuid.toObject(includeInstance, f) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.dlt.DltRecordId} + */ +proto.dlt.DltRecordId.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.dlt.DltRecordId; + return proto.dlt.DltRecordId.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.dlt.DltRecordId} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.dlt.DltRecordId} + */ +proto.dlt.DltRecordId.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new context_pb.Uuid; + reader.readMessage(value,context_pb.Uuid.deserializeBinaryFromReader); + msg.setDomainUuid(value); + break; + case 2: + var value = /** @type {!proto.dlt.DltRecordTypeEnum} */ (reader.readEnum()); + msg.setType(value); + break; + case 3: + var value = new context_pb.Uuid; + reader.readMessage(value,context_pb.Uuid.deserializeBinaryFromReader); + msg.setRecordUuid(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.dlt.DltRecordId.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.dlt.DltRecordId.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.dlt.DltRecordId} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltRecordId.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getDomainUuid(); + if (f != null) { + writer.writeMessage( + 1, + f, + context_pb.Uuid.serializeBinaryToWriter + ); + } + f = message.getType(); + if (f !== 0.0) { + writer.writeEnum( + 2, + f + ); + } + f = message.getRecordUuid(); + if (f != null) { + writer.writeMessage( + 3, + f, + context_pb.Uuid.serializeBinaryToWriter + ); + } +}; + + +/** + * optional context.Uuid domain_uuid = 1; + * @return {?proto.context.Uuid} + */ +proto.dlt.DltRecordId.prototype.getDomainUuid = function() { + return /** @type{?proto.context.Uuid} */ ( + jspb.Message.getWrapperField(this, context_pb.Uuid, 1)); +}; + + +/** + * @param {?proto.context.Uuid|undefined} value + * @return {!proto.dlt.DltRecordId} returns this +*/ +proto.dlt.DltRecordId.prototype.setDomainUuid = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.dlt.DltRecordId} returns this + */ +proto.dlt.DltRecordId.prototype.clearDomainUuid = function() { + return this.setDomainUuid(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.dlt.DltRecordId.prototype.hasDomainUuid = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional DltRecordTypeEnum type = 2; + * @return {!proto.dlt.DltRecordTypeEnum} + */ +proto.dlt.DltRecordId.prototype.getType = function() { + return /** @type {!proto.dlt.DltRecordTypeEnum} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {!proto.dlt.DltRecordTypeEnum} value + * @return {!proto.dlt.DltRecordId} returns this + */ +proto.dlt.DltRecordId.prototype.setType = function(value) { + return jspb.Message.setProto3EnumField(this, 2, value); +}; + + +/** + * optional context.Uuid record_uuid = 3; + * @return {?proto.context.Uuid} + */ +proto.dlt.DltRecordId.prototype.getRecordUuid = function() { + return /** @type{?proto.context.Uuid} */ ( + jspb.Message.getWrapperField(this, context_pb.Uuid, 3)); +}; + + +/** + * @param {?proto.context.Uuid|undefined} value + * @return {!proto.dlt.DltRecordId} returns this +*/ +proto.dlt.DltRecordId.prototype.setRecordUuid = function(value) { + return jspb.Message.setWrapperField(this, 3, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.dlt.DltRecordId} returns this + */ +proto.dlt.DltRecordId.prototype.clearRecordUuid = function() { + return this.setRecordUuid(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.dlt.DltRecordId.prototype.hasRecordUuid = function() { + return jspb.Message.getField(this, 3) != null; +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.dlt.DltRecord.prototype.toObject = function(opt_includeInstance) { + return proto.dlt.DltRecord.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.dlt.DltRecord} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltRecord.toObject = function(includeInstance, msg) { + var f, obj = { + recordId: (f = msg.getRecordId()) && proto.dlt.DltRecordId.toObject(includeInstance, f), + operation: jspb.Message.getFieldWithDefault(msg, 2, 0), + dataJson: jspb.Message.getFieldWithDefault(msg, 3, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.dlt.DltRecord} + */ +proto.dlt.DltRecord.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.dlt.DltRecord; + return proto.dlt.DltRecord.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.dlt.DltRecord} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.dlt.DltRecord} + */ +proto.dlt.DltRecord.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.dlt.DltRecordId; + reader.readMessage(value,proto.dlt.DltRecordId.deserializeBinaryFromReader); + msg.setRecordId(value); + break; + case 2: + var value = /** @type {!proto.dlt.DltRecordOperationEnum} */ (reader.readEnum()); + msg.setOperation(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setDataJson(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.dlt.DltRecord.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.dlt.DltRecord.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.dlt.DltRecord} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltRecord.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getRecordId(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.dlt.DltRecordId.serializeBinaryToWriter + ); + } + f = message.getOperation(); + if (f !== 0.0) { + writer.writeEnum( + 2, + f + ); + } + f = message.getDataJson(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } +}; + + +/** + * optional DltRecordId record_id = 1; + * @return {?proto.dlt.DltRecordId} + */ +proto.dlt.DltRecord.prototype.getRecordId = function() { + return /** @type{?proto.dlt.DltRecordId} */ ( + jspb.Message.getWrapperField(this, proto.dlt.DltRecordId, 1)); +}; + + +/** + * @param {?proto.dlt.DltRecordId|undefined} value + * @return {!proto.dlt.DltRecord} returns this +*/ +proto.dlt.DltRecord.prototype.setRecordId = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.dlt.DltRecord} returns this + */ +proto.dlt.DltRecord.prototype.clearRecordId = function() { + return this.setRecordId(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.dlt.DltRecord.prototype.hasRecordId = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional DltRecordOperationEnum operation = 2; + * @return {!proto.dlt.DltRecordOperationEnum} + */ +proto.dlt.DltRecord.prototype.getOperation = function() { + return /** @type {!proto.dlt.DltRecordOperationEnum} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {!proto.dlt.DltRecordOperationEnum} value + * @return {!proto.dlt.DltRecord} returns this + */ +proto.dlt.DltRecord.prototype.setOperation = function(value) { + return jspb.Message.setProto3EnumField(this, 2, value); +}; + + +/** + * optional string data_json = 3; + * @return {string} + */ +proto.dlt.DltRecord.prototype.getDataJson = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.dlt.DltRecord} returns this + */ +proto.dlt.DltRecord.prototype.setDataJson = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + + +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.dlt.DltRecordSubscription.repeatedFields_ = [1,2]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.dlt.DltRecordSubscription.prototype.toObject = function(opt_includeInstance) { + return proto.dlt.DltRecordSubscription.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.dlt.DltRecordSubscription} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltRecordSubscription.toObject = function(includeInstance, msg) { + var f, obj = { + typeList: (f = jspb.Message.getRepeatedField(msg, 1)) == null ? undefined : f, + operationList: (f = jspb.Message.getRepeatedField(msg, 2)) == null ? undefined : f + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.dlt.DltRecordSubscription} + */ +proto.dlt.DltRecordSubscription.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.dlt.DltRecordSubscription; + return proto.dlt.DltRecordSubscription.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.dlt.DltRecordSubscription} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.dlt.DltRecordSubscription} + */ +proto.dlt.DltRecordSubscription.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var values = /** @type {!Array} */ (reader.isDelimited() ? reader.readPackedEnum() : [reader.readEnum()]); + for (var i = 0; i < values.length; i++) { + msg.addType(values[i]); + } + break; + case 2: + var values = /** @type {!Array} */ (reader.isDelimited() ? reader.readPackedEnum() : [reader.readEnum()]); + for (var i = 0; i < values.length; i++) { + msg.addOperation(values[i]); + } + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.dlt.DltRecordSubscription.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.dlt.DltRecordSubscription.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.dlt.DltRecordSubscription} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltRecordSubscription.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getTypeList(); + if (f.length > 0) { + writer.writePackedEnum( + 1, + f + ); + } + f = message.getOperationList(); + if (f.length > 0) { + writer.writePackedEnum( + 2, + f + ); + } +}; + + +/** + * repeated DltRecordTypeEnum type = 1; + * @return {!Array} + */ +proto.dlt.DltRecordSubscription.prototype.getTypeList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 1)); +}; + + +/** + * @param {!Array} value + * @return {!proto.dlt.DltRecordSubscription} returns this + */ +proto.dlt.DltRecordSubscription.prototype.setTypeList = function(value) { + return jspb.Message.setField(this, 1, value || []); +}; + + +/** + * @param {!proto.dlt.DltRecordTypeEnum} value + * @param {number=} opt_index + * @return {!proto.dlt.DltRecordSubscription} returns this + */ +proto.dlt.DltRecordSubscription.prototype.addType = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 1, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.dlt.DltRecordSubscription} returns this + */ +proto.dlt.DltRecordSubscription.prototype.clearTypeList = function() { + return this.setTypeList([]); +}; + + +/** + * repeated DltRecordOperationEnum operation = 2; + * @return {!Array} + */ +proto.dlt.DltRecordSubscription.prototype.getOperationList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 2)); +}; + + +/** + * @param {!Array} value + * @return {!proto.dlt.DltRecordSubscription} returns this + */ +proto.dlt.DltRecordSubscription.prototype.setOperationList = function(value) { + return jspb.Message.setField(this, 2, value || []); +}; + + +/** + * @param {!proto.dlt.DltRecordOperationEnum} value + * @param {number=} opt_index + * @return {!proto.dlt.DltRecordSubscription} returns this + */ +proto.dlt.DltRecordSubscription.prototype.addOperation = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 2, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.dlt.DltRecordSubscription} returns this + */ +proto.dlt.DltRecordSubscription.prototype.clearOperationList = function() { + return this.setOperationList([]); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.dlt.DltRecordEvent.prototype.toObject = function(opt_includeInstance) { + return proto.dlt.DltRecordEvent.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.dlt.DltRecordEvent} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltRecordEvent.toObject = function(includeInstance, msg) { + var f, obj = { + event: (f = msg.getEvent()) && context_pb.Event.toObject(includeInstance, f), + recordId: (f = msg.getRecordId()) && proto.dlt.DltRecordId.toObject(includeInstance, f) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.dlt.DltRecordEvent} + */ +proto.dlt.DltRecordEvent.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.dlt.DltRecordEvent; + return proto.dlt.DltRecordEvent.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.dlt.DltRecordEvent} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.dlt.DltRecordEvent} + */ +proto.dlt.DltRecordEvent.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new context_pb.Event; + reader.readMessage(value,context_pb.Event.deserializeBinaryFromReader); + msg.setEvent(value); + break; + case 2: + var value = new proto.dlt.DltRecordId; + reader.readMessage(value,proto.dlt.DltRecordId.deserializeBinaryFromReader); + msg.setRecordId(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.dlt.DltRecordEvent.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.dlt.DltRecordEvent.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.dlt.DltRecordEvent} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltRecordEvent.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getEvent(); + if (f != null) { + writer.writeMessage( + 1, + f, + context_pb.Event.serializeBinaryToWriter + ); + } + f = message.getRecordId(); + if (f != null) { + writer.writeMessage( + 2, + f, + proto.dlt.DltRecordId.serializeBinaryToWriter + ); + } +}; + + +/** + * optional context.Event event = 1; + * @return {?proto.context.Event} + */ +proto.dlt.DltRecordEvent.prototype.getEvent = function() { + return /** @type{?proto.context.Event} */ ( + jspb.Message.getWrapperField(this, context_pb.Event, 1)); +}; + + +/** + * @param {?proto.context.Event|undefined} value + * @return {!proto.dlt.DltRecordEvent} returns this +*/ +proto.dlt.DltRecordEvent.prototype.setEvent = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.dlt.DltRecordEvent} returns this + */ +proto.dlt.DltRecordEvent.prototype.clearEvent = function() { + return this.setEvent(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.dlt.DltRecordEvent.prototype.hasEvent = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional DltRecordId record_id = 2; + * @return {?proto.dlt.DltRecordId} + */ +proto.dlt.DltRecordEvent.prototype.getRecordId = function() { + return /** @type{?proto.dlt.DltRecordId} */ ( + jspb.Message.getWrapperField(this, proto.dlt.DltRecordId, 2)); +}; + + +/** + * @param {?proto.dlt.DltRecordId|undefined} value + * @return {!proto.dlt.DltRecordEvent} returns this +*/ +proto.dlt.DltRecordEvent.prototype.setRecordId = function(value) { + return jspb.Message.setWrapperField(this, 2, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.dlt.DltRecordEvent} returns this + */ +proto.dlt.DltRecordEvent.prototype.clearRecordId = function() { + return this.setRecordId(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.dlt.DltRecordEvent.prototype.hasRecordId = function() { + return jspb.Message.getField(this, 2) != null; +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.dlt.DltRecordStatus.prototype.toObject = function(opt_includeInstance) { + return proto.dlt.DltRecordStatus.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.dlt.DltRecordStatus} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltRecordStatus.toObject = function(includeInstance, msg) { + var f, obj = { + recordId: (f = msg.getRecordId()) && proto.dlt.DltRecordId.toObject(includeInstance, f), + status: jspb.Message.getFieldWithDefault(msg, 2, 0), + errorMessage: jspb.Message.getFieldWithDefault(msg, 3, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.dlt.DltRecordStatus} + */ +proto.dlt.DltRecordStatus.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.dlt.DltRecordStatus; + return proto.dlt.DltRecordStatus.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.dlt.DltRecordStatus} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.dlt.DltRecordStatus} + */ +proto.dlt.DltRecordStatus.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.dlt.DltRecordId; + reader.readMessage(value,proto.dlt.DltRecordId.deserializeBinaryFromReader); + msg.setRecordId(value); + break; + case 2: + var value = /** @type {!proto.dlt.DltRecordStatusEnum} */ (reader.readEnum()); + msg.setStatus(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setErrorMessage(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.dlt.DltRecordStatus.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.dlt.DltRecordStatus.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.dlt.DltRecordStatus} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltRecordStatus.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getRecordId(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.dlt.DltRecordId.serializeBinaryToWriter + ); + } + f = message.getStatus(); + if (f !== 0.0) { + writer.writeEnum( + 2, + f + ); + } + f = message.getErrorMessage(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } +}; + + +/** + * optional DltRecordId record_id = 1; + * @return {?proto.dlt.DltRecordId} + */ +proto.dlt.DltRecordStatus.prototype.getRecordId = function() { + return /** @type{?proto.dlt.DltRecordId} */ ( + jspb.Message.getWrapperField(this, proto.dlt.DltRecordId, 1)); +}; + + +/** + * @param {?proto.dlt.DltRecordId|undefined} value + * @return {!proto.dlt.DltRecordStatus} returns this +*/ +proto.dlt.DltRecordStatus.prototype.setRecordId = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.dlt.DltRecordStatus} returns this + */ +proto.dlt.DltRecordStatus.prototype.clearRecordId = function() { + return this.setRecordId(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.dlt.DltRecordStatus.prototype.hasRecordId = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional DltRecordStatusEnum status = 2; + * @return {!proto.dlt.DltRecordStatusEnum} + */ +proto.dlt.DltRecordStatus.prototype.getStatus = function() { + return /** @type {!proto.dlt.DltRecordStatusEnum} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {!proto.dlt.DltRecordStatusEnum} value + * @return {!proto.dlt.DltRecordStatus} returns this + */ +proto.dlt.DltRecordStatus.prototype.setStatus = function(value) { + return jspb.Message.setProto3EnumField(this, 2, value); +}; + + +/** + * optional string error_message = 3; + * @return {string} + */ +proto.dlt.DltRecordStatus.prototype.getErrorMessage = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.dlt.DltRecordStatus} returns this + */ +proto.dlt.DltRecordStatus.prototype.setErrorMessage = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.dlt.DltPeerStatus.prototype.toObject = function(opt_includeInstance) { + return proto.dlt.DltPeerStatus.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.dlt.DltPeerStatus} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltPeerStatus.toObject = function(includeInstance, msg) { + var f, obj = { + controller: (f = msg.getController()) && context_pb.TeraFlowController.toObject(includeInstance, f), + status: jspb.Message.getFieldWithDefault(msg, 2, 0) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.dlt.DltPeerStatus} + */ +proto.dlt.DltPeerStatus.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.dlt.DltPeerStatus; + return proto.dlt.DltPeerStatus.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.dlt.DltPeerStatus} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.dlt.DltPeerStatus} + */ +proto.dlt.DltPeerStatus.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new context_pb.TeraFlowController; + reader.readMessage(value,context_pb.TeraFlowController.deserializeBinaryFromReader); + msg.setController(value); + break; + case 2: + var value = /** @type {!proto.dlt.DltStatusEnum} */ (reader.readEnum()); + msg.setStatus(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.dlt.DltPeerStatus.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.dlt.DltPeerStatus.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.dlt.DltPeerStatus} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltPeerStatus.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getController(); + if (f != null) { + writer.writeMessage( + 1, + f, + context_pb.TeraFlowController.serializeBinaryToWriter + ); + } + f = message.getStatus(); + if (f !== 0.0) { + writer.writeEnum( + 2, + f + ); + } +}; + + +/** + * optional context.TeraFlowController controller = 1; + * @return {?proto.context.TeraFlowController} + */ +proto.dlt.DltPeerStatus.prototype.getController = function() { + return /** @type{?proto.context.TeraFlowController} */ ( + jspb.Message.getWrapperField(this, context_pb.TeraFlowController, 1)); +}; + + +/** + * @param {?proto.context.TeraFlowController|undefined} value + * @return {!proto.dlt.DltPeerStatus} returns this +*/ +proto.dlt.DltPeerStatus.prototype.setController = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.dlt.DltPeerStatus} returns this + */ +proto.dlt.DltPeerStatus.prototype.clearController = function() { + return this.setController(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.dlt.DltPeerStatus.prototype.hasController = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional DltStatusEnum status = 2; + * @return {!proto.dlt.DltStatusEnum} + */ +proto.dlt.DltPeerStatus.prototype.getStatus = function() { + return /** @type {!proto.dlt.DltStatusEnum} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {!proto.dlt.DltStatusEnum} value + * @return {!proto.dlt.DltPeerStatus} returns this + */ +proto.dlt.DltPeerStatus.prototype.setStatus = function(value) { + return jspb.Message.setProto3EnumField(this, 2, value); +}; + + + +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.dlt.DltPeerStatusList.repeatedFields_ = [1]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.dlt.DltPeerStatusList.prototype.toObject = function(opt_includeInstance) { + return proto.dlt.DltPeerStatusList.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.dlt.DltPeerStatusList} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltPeerStatusList.toObject = function(includeInstance, msg) { + var f, obj = { + peersList: jspb.Message.toObjectList(msg.getPeersList(), + proto.dlt.DltPeerStatus.toObject, includeInstance) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.dlt.DltPeerStatusList} + */ +proto.dlt.DltPeerStatusList.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.dlt.DltPeerStatusList; + return proto.dlt.DltPeerStatusList.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.dlt.DltPeerStatusList} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.dlt.DltPeerStatusList} + */ +proto.dlt.DltPeerStatusList.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.dlt.DltPeerStatus; + reader.readMessage(value,proto.dlt.DltPeerStatus.deserializeBinaryFromReader); + msg.addPeers(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.dlt.DltPeerStatusList.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.dlt.DltPeerStatusList.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.dlt.DltPeerStatusList} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.dlt.DltPeerStatusList.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getPeersList(); + if (f.length > 0) { + writer.writeRepeatedMessage( + 1, + f, + proto.dlt.DltPeerStatus.serializeBinaryToWriter + ); + } +}; + + +/** + * repeated DltPeerStatus peers = 1; + * @return {!Array} + */ +proto.dlt.DltPeerStatusList.prototype.getPeersList = function() { + return /** @type{!Array} */ ( + jspb.Message.getRepeatedWrapperField(this, proto.dlt.DltPeerStatus, 1)); +}; + + +/** + * @param {!Array} value + * @return {!proto.dlt.DltPeerStatusList} returns this +*/ +proto.dlt.DltPeerStatusList.prototype.setPeersList = function(value) { + return jspb.Message.setRepeatedWrapperField(this, 1, value); +}; + + +/** + * @param {!proto.dlt.DltPeerStatus=} opt_value + * @param {number=} opt_index + * @return {!proto.dlt.DltPeerStatus} + */ +proto.dlt.DltPeerStatusList.prototype.addPeers = function(opt_value, opt_index) { + return jspb.Message.addToRepeatedWrapperField(this, 1, opt_value, proto.dlt.DltPeerStatus, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.dlt.DltPeerStatusList} returns this + */ +proto.dlt.DltPeerStatusList.prototype.clearPeersList = function() { + return this.setPeersList([]); +}; + + +/** + * @enum {number} + */ +proto.dlt.DltRecordTypeEnum = { + DLTRECORDTYPE_UNDEFINED: 0, + DLTRECORDTYPE_CONTEXT: 1, + DLTRECORDTYPE_TOPOLOGY: 2, + DLTRECORDTYPE_DEVICE: 3, + DLTRECORDTYPE_LINK: 4, + DLTRECORDTYPE_SERVICE: 5, + DLTRECORDTYPE_SLICE: 6 +}; + +/** + * @enum {number} + */ +proto.dlt.DltRecordOperationEnum = { + DLTRECORDOPERATION_UNDEFINED: 0, + DLTRECORDOPERATION_ADD: 1, + DLTRECORDOPERATION_UPDATE: 2, + DLTRECORDOPERATION_DELETE: 3 +}; + +/** + * @enum {number} + */ +proto.dlt.DltRecordStatusEnum = { + DLTRECORDSTATUS_UNDEFINED: 0, + DLTRECORDSTATUS_SUCCEEDED: 1, + DLTRECORDSTATUS_FAILED: 2 +}; + +/** + * @enum {number} + */ +proto.dlt.DltStatusEnum = { + DLTSTATUS_UNDEFINED: 0, + DLTSTATUS_NOTAVAILABLE: 1, + DLTSTATUS_INITIALIZED: 2, + DLTSTATUS_AVAILABLE: 3, + DLTSTATUS_DEINIT: 4 +}; + +goog.object.extend(exports, proto.dlt); diff --git a/src/dlt/gateway/dltApp/src/dltGateway.js b/src/dlt/gateway/dltApp/src/dltGateway.js new file mode 100644 index 000000000..8c9e79663 --- /dev/null +++ b/src/dlt/gateway/dltApp/src/dltGateway.js @@ -0,0 +1,237 @@ +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); +const path = require('path'); +const { connectToNetwork } = require('../dist/fabricConnect'); +const utf8Decoder = new TextDecoder(); + +// Load the protocol buffer definitions +const PROTO_PATH = path.resolve(__dirname, '../proto/dlt_service.proto'); +const packageDefinition = protoLoader.loadSync(PROTO_PATH, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, +}); +const dltProto = grpc.loadPackageDefinition(packageDefinition).dlt; + +// Create a gRPC server instance +const server = new grpc.Server(); +let contractInstance = null; +let closeConnection = null; +let events = null; // To store the events iterable + +const clients = new Set(); // Set to keep track of active client streams + + + +// Initialize connection to the chaincode + +async function initChaincodeConnection() { + try { + const networkResources = await connectToNetwork(); + contractInstance = networkResources.contract; + events = networkResources.events; // Initiate event listening + closeConnection = networkResources.close; + + //console.log("DEBUG", events) + console.log("Chaincode connection established successfully."); + } catch (error) { + console.error('Failed to establish chaincode connection:', error); + process.exit(1); // Exit if the connection cannot be established + } +} + +// gRPC method to handle recording data to the DLT +async function recordToDlt(call, callback) { + if (!contractInstance) { + callback({ + code: grpc.status.UNAVAILABLE, + details: "Chaincode connection is not established." + }); + return; + } + const { record_id, operation, data_json } = call.request; + try { + + + console.log(`Operation requested: ${operation}`); + + switch (operation) { + case 'DLTRECORDOPERATION_ADD': + await contractInstance.submitTransaction('StoreRecord', JSON.stringify(record_id), data_json); + break; + case 'DLTRECORDOPERATION_UPDATE': + await contractInstance.submitTransaction('UpdateRecord', JSON.stringify(record_id), data_json); + break; + case 'DLTRECORDOPERATION_DELETE': + await contractInstance.submitTransaction('DeleteRecord', JSON.stringify(record_id)); + break; + default: + throw new Error('Invalid operation'); + } + // Send success response + callback(null, { record_id, status: 'DLTRECORDSTATUS_SUCCEEDED' }); + } catch (error) { + // Send failure response with error message + //console.log("ERRROR", error) + callback(null, { record_id, status: 'DLTRECORDSTATUS_FAILED', error_message: error.message }); + } +} + +// gRPC method to fetch data from the DLT +async function getFromDlt(call, callback) { + + if (!contractInstance) { + callback({ + code: grpc.status.UNAVAILABLE, + details: "Chaincode connection is not established." + }); + return; + } + + try { + + console.log("RECEIVED CALL REQUEST:", call.request); + const { record_uuid } = call.request; + const resultBytes = await contractInstance.evaluateTransaction('RetrieveRecord', JSON.stringify(call.request)); + // Decode and parse the result + const resultJson = utf8Decoder.decode(resultBytes); + const result = JSON.parse(resultJson); + + // Send the response with the formatted JSON data + callback(null, { record_id: call.request, data_json: JSON.stringify(result) }); + } catch (error) { + // Send failure response with error message + callback({ + code: grpc.status.UNKNOWN, + details: error.message + }); + } +} + +// Implement subscription to DLT events + +const eventNameToEventTypeEnum = { + 'StoreRecord': 'EVENTTYPE_CREATE', + 'UpdateRecord': 'EVENTTYPE_UPDATE', + 'DeleteRecord': 'EVENTTYPE_REMOVE' +}; + +function subscribeToDlt(call) { + if (!events) { + call.emit('error', { + code: grpc.status.UNAVAILABLE, + details: "Event listener is not established." + }); + return; + } + + // Add the client to the set of active clients + clients.add(call); + console.log(`Client connected. Total clients: ${clients.size}`); + + // Clean up when the client disconnects + call.on('cancelled', () => { + clients.delete(call); + console.log(`Client disconnected (cancelled). Total clients: ${clients.size}`); + + }); + call.on('error', (err) => { + clients.delete(call); + console.log(`Client disconnected (error: ${err.message}). Total clients: ${clients.size}`); + + }); + call.on('end', () => { + clients.delete(call); + console.log(`Client disconnected (end). Total clients: ${clients.size}`); + + }); + + (async () => { + try { + for await (const event of events) { + const eventPayload = event.payload; + //console.log("Raw event payload:", eventPayload); + const resultJson = utf8Decoder.decode(eventPayload); + const eventJson = JSON.parse(resultJson); + + console.log("Writing event to stream:", eventJson.record_id); + + const eventType = eventNameToEventTypeEnum[event.eventName] || 'EVENTTYPE_UNDEFINED'; + + for (const client of clients) { + const writeSuccessful = client.write({ + event: { + timestamp: { timestamp: Math.floor(Date.now() / 1000) }, + event_type: eventType // Set appropriate event type + }, + record_id: { + domain_uuid: { uuid: eventJson.record_id.domain_uuid.uuid }, + type: eventJson.record_id.type || 'DLTRECORDTYPE_UNDEFINED', + record_uuid: { uuid: eventJson.record_id.record_uuid.uuid } + }, + data_json: JSON.stringify(eventJson) + }); + + // Check if the internal buffer is full + if (!writeSuccessful) { + // Wait for the 'drain' event before continuing + await new Promise((resolve) => client.once('drain', resolve)); + } + } + } + + //call.end(); + + } catch (error) { + for (const client of clients) { + client.emit('error', { + code: grpc.status.UNKNOWN, + details: `Error processing event: ${error.message}` + }); + + } + } + })(); +} + +// Placeholder +function getDltStatus(call, callback) { + // Implement status fetching logic here + // Not implemented for simplicity +} + +// Placeholder +function getDltPeers(call, callback) { + // Implement peers fetching logic here + // Not implemented for simplicity +} + +// Add the service to the server +server.addService(dltProto.DltGatewayService.service, { + RecordToDlt: recordToDlt, + GetFromDlt: getFromDlt, + SubscribeToDlt: subscribeToDlt, + GetDltStatus: getDltStatus, + GetDltPeers: getDltPeers, +}); + +// Start the server +const PORT = process.env.GRPC_PORT || '50051'; +server.bindAsync(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure(), async (error) => { + if (error) { + console.error('Failed to bind server:', error); + return; + } + console.log(`gRPC server running at http://0.0.0.0:${PORT}`); + await initChaincodeConnection(); //Connects to the chaincode and + server.start(); +}); + +// Handle shutdown gracefully +process.on('SIGINT', async () => { + console.log('Shutting down...'); + await closeConnection(); + server.forceShutdown(); +}); \ No newline at end of file diff --git a/src/dlt/gateway/dltApp/src/fabricConnect.ts b/src/dlt/gateway/dltApp/src/fabricConnect.ts new file mode 100644 index 000000000..51c8f8046 --- /dev/null +++ b/src/dlt/gateway/dltApp/src/fabricConnect.ts @@ -0,0 +1,201 @@ +/* + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as grpc from '@grpc/grpc-js'; +import { connect, Contract, Identity, Signer, signers, Network, CloseableAsyncIterable, ChaincodeEvent, GatewayError } from '@hyperledger/fabric-gateway'; +import * as crypto from 'crypto'; +import { promises as fs } from 'fs'; +import * as path from 'path'; +import { TextDecoder } from 'util'; +import * as dotenv from 'dotenv'; + +dotenv.config({ path: path.resolve(__dirname, '..', '.env') }); +const channelName = envOrDefault('CHANNEL_NAME', 'channel1'); +const chaincodeName = envOrDefault('CHAINCODE_NAME', 'adrenalineDLT'); +const mspId = envOrDefault('MSP_ID', 'Org1MSP'); + +// Path to crypto materials. +const cryptoPath = envOrDefault('CRYPTO_PATH', path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.adrenaline.com')); + +// Path to user private key directory. +const keyDirectoryPath = envOrDefault('KEY_DIRECTORY_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.adrenaline.com', 'msp', 'keystore')); + +// Path to user certificate directory. +const certDirectoryPath = envOrDefault('CERT_DIRECTORY_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.adrenaline.com', 'msp', 'signcerts')); + +// Path to peer tls certificate. +const tlsCertPath = envOrDefault('TLS_CERT_PATH', path.resolve(cryptoPath, 'peers', 'peer1.org1.adrenaline.com', 'tls', 'ca.crt')); + +// Gateway peer endpoint. +const peerEndpoint = envOrDefault('PEER_ENDPOINT', 'localhost:7051'); + +// Gateway peer SSL host name override. +const peerHostAlias = envOrDefault('PEER_HOST_ALIAS', 'peer1.org1.adrenaline.com'); + +const utf8Decoder = new TextDecoder(); +const assetId = `asset${Date.now()}`; + +export async function connectToNetwork(): Promise<{ contract: Contract, events: CloseableAsyncIterable, close: () => Promise }> { + + await displayInputParameters(); + + // The gRPC client connection should be shared by all Gateway connections to this endpoint. + + const client = await newGrpcConnection(); + + const gateway = connect({ + client, + identity: await newIdentity(), + signer: await newSigner(), + // Default timeouts for different gRPC calls + evaluateOptions: () => { + return { deadline: Date.now() + 5000 }; // 5 seconds + }, + endorseOptions: () => { + return { deadline: Date.now() + 15000 }; // 15 seconds + }, + submitOptions: () => { + return { deadline: Date.now() + 5000 }; // 5 seconds + }, + commitStatusOptions: () => { + return { deadline: Date.now() + 60000 }; // 1 minute + }, + }); + + let events: CloseableAsyncIterable | undefined; + + + // Get a network instance representing the channel where the smart contract is deployed. + const network = gateway.getNetwork(channelName); + + // Get the smart contract from the network. + const contract = network.getContract(chaincodeName); + + //Listen for events emitted by transactions + //events = await startEventListening(network); + events = await network.getChaincodeEvents(chaincodeName); + + // Initialize the ledger. + await initLedger(contract); + + console.log(Date.now()) + + return { + contract: contract, + events: events, + close: async function () { + if (events) events.close(); + gateway.close(); + client.close(); + } + }; + + +} + +async function newGrpcConnection(): Promise { + const tlsRootCert = await fs.readFile(tlsCertPath); + const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); + return new grpc.Client(peerEndpoint, tlsCredentials, { + 'grpc.ssl_target_name_override': peerHostAlias, + }); +} + +async function newIdentity(): Promise { + const certPath = await getFirstDirFileName(certDirectoryPath); + const credentials = await fs.readFile(certPath); + return { mspId, credentials }; +} + +async function getFirstDirFileName(dirPath: string): Promise { + const files = await fs.readdir(dirPath); + return path.join(dirPath, files[0]); +} + +async function newSigner(): Promise { + const keyPath = await getFirstDirFileName(keyDirectoryPath); + const privateKeyPem = await fs.readFile(keyPath); + const privateKey = crypto.createPrivateKey(privateKeyPem); + return signers.newPrivateKeySigner(privateKey); +} + +/** + * This type of transaction would typically only be run once by an application the first time it was started after its + * initial deployment. A new version of the chaincode deployed later would likely not need to run an "init" function. + */ +async function initLedger(contract: Contract): Promise { + try { + console.log('\n--> Submit Transaction: InitLedger, function activates the chaincode'); + + await contract.submitTransaction('InitLedger'); + + console.log('*** Transaction committed successfully'); + } catch (error) { + console.error('Failed to submit InitLedger transaction:', error); + throw error; + } +} + + + +/** + * envOrDefault() will return the value of an environment variable, or a default value if the variable is undefined. + */ +function envOrDefault(key: string, defaultValue: string): string { + return process.env[key] || defaultValue; +} + +/** + * displayInputParameters() will print the global scope parameters used by the main driver routine. + */ +async function displayInputParameters(): Promise { + console.log(`channelName: ${channelName}`); + console.log(`chaincodeName: ${chaincodeName}`); + console.log(`mspId: ${mspId}`); + console.log(`cryptoPath: ${cryptoPath}`); + console.log(`keyDirectoryPath: ${keyDirectoryPath}`); + console.log(`certDirectoryPath: ${certDirectoryPath}`); + console.log(`tlsCertPath: ${tlsCertPath}`); + console.log(`peerEndpoint: ${peerEndpoint}`); + console.log(`peerHostAlias: ${peerHostAlias}`); +} + +/** + * startEventListening() will initiate the event listener for chaincode events. + */ +async function startEventListening(network: Network): Promise> { + console.log('\n*** Start chaincode event listening'); + + const events = await network.getChaincodeEvents(chaincodeName); + + void readEvents(events); // Don't await - run asynchronously + return events; +} + +/** + * readEvents() format and display the events as a JSON. + */ +async function readEvents(events: CloseableAsyncIterable): Promise { + try { + for await (const event of events) { + const payload = parseJson(event.payload); + console.log(`\n<-- Chaincode event received: ${event.eventName} -`, payload); + } + } catch (error: unknown) { + // Ignore the read error when events.close() is called explicitly + if (!(error instanceof GatewayError) || error.code !== grpc.status.CANCELLED.valueOf()) { + throw error; + } + } +} + +/** + * parseJson() formats a JSON. + */ +function parseJson(jsonBytes: Uint8Array): unknown { + const json = utf8Decoder.decode(jsonBytes); + return JSON.parse(json); +} + diff --git a/src/dlt/gateway/dltApp/src/testEvents.js b/src/dlt/gateway/dltApp/src/testEvents.js new file mode 100644 index 000000000..68573e271 --- /dev/null +++ b/src/dlt/gateway/dltApp/src/testEvents.js @@ -0,0 +1,51 @@ +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); +const path = require('path'); + +const PROTO_PATH = path.resolve(__dirname, '../proto/dlt_service.proto'); +const packageDefinition = protoLoader.loadSync(PROTO_PATH, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, +}); +const dltProto = grpc.loadPackageDefinition(packageDefinition).dlt; + +const client = new dltProto.DltGatewayService( + '10.1.1.96:32001', + grpc.credentials.createInsecure() +); + +function subscribeToDlt() { + const request = { + // Define any necessary subscription filters here if applicable + }; + + const call = client.SubscribeToDlt(request); + + call.on('data', (event) => { + console.log('Received event:', event); + }); + + call.on('error', (error) => { + console.error('Error:', error.message); + }); + + call.on('end', () => { + console.log('Stream ended.'); + }); + + // Optionally, you can cancel the subscription after a certain time or condition + setTimeout(() => { + console.log('Cancelling subscription...'); + call.cancel(); + }, 600000); // Cancel after 1 minute for demonstration purposes +} + +function runTests() { + console.log("Testing subscription to DLT events"); + subscribeToDlt(); +} + +runTests(); diff --git a/src/dlt/gateway/dltApp/src/testGateway.js b/src/dlt/gateway/dltApp/src/testGateway.js new file mode 100644 index 000000000..3486cb4f3 --- /dev/null +++ b/src/dlt/gateway/dltApp/src/testGateway.js @@ -0,0 +1,111 @@ +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); +const path = require('path'); +const fs = require('fs').promises; +const { v4: uuidv4 } = require('uuid'); // Import the UUID library + + +const PROTO_PATH = path.resolve(__dirname, '../proto/dlt_service.proto'); +const packageDefinition = protoLoader.loadSync(PROTO_PATH, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, +}); +const dltProto = grpc.loadPackageDefinition(packageDefinition).dlt; + +const client = new dltProto.DltGatewayService( + '10.1.1.96:32001', + grpc.credentials.createInsecure() +); + +const assetId = `asset-${Date.now()}`; +const domainUuid = `domain-${uuidv4()}`; // Generate a pretty domain UUID + +async function getTopoData(filename) { + try { + const data = await fs.readFile(`../../samples/${filename}`, 'utf8'); + return data; + } catch (error) { + console.error('Failed to read file:', filename, error); + return '{}'; // Empty JSON if error + } +} + +async function processTopoData(operation, assetId, jsonFilename) { + let jsonData = '{}'; + if (jsonFilename) { + jsonData = await getTopoData(jsonFilename); + } + + const request = { + record_id: { + domain_uuid: { uuid: domainUuid }, // Replace "domain-uuid" with actual domain UUID if needed + type: 'DLTRECORDTYPE_TOPOLOGY', // Use the appropriate type if needed + record_uuid: { uuid: assetId } + }, + operation, + data_json: jsonData + }; + + return new Promise((resolve, reject) => { + client.RecordToDlt(request, (error, response) => { + if (error) { + console.error('Error:', error.message); + reject(error); + } else { + console.log('Response:', response); + resolve(response); + } + }); + }); +} + +async function getDLTData(assetId) { + + const request = { + domain_uuid: { uuid: domainUuid }, // Replace "domain-uuid" with actual domain UUID if needed + type: 'DLTRECORDTYPE_TOPOLOGY', // Use the appropriate type if needed + record_uuid: { uuid: assetId } + }; + + return new Promise((resolve, reject) => { + client.GetFromDlt(request, (error, response) => { + if (error) { + console.error('Error:', error.message); + reject(error); + } else { + console.log('Response:', response); + resolve(response); + } + }); + }); +} + +async function runTests() { + console.log("Testing Store Operation"); + await processTopoData('DLTRECORDOPERATION_ADD', assetId, 'topo2.json'); + + console.log("Testing Update Operation"); + await processTopoData('DLTRECORDOPERATION_UPDATE', assetId, 'topo3.json'); + + console.log("Testing Fetch Operation"); + await getDLTData(assetId); + + + console.log("Testing Delete Operation"); + await processTopoData('DLTRECORDOPERATION_DELETE', assetId); + + console.log("Testing Fetch All Operation"); + // This part assumes you have a GetAllInfo method implemented in your chaincode and corresponding gRPC service. + // client.GetAllInfo({}, (error, response) => { + // if (error) { + // console.error('Error:', error.message); + // } else { + // console.log('All Data:', response); + // } + // }); +} + +runTests().catch(console.error); diff --git a/src/dlt/gateway/dltApp/tests/dltApp_Test/.gitignore b/src/dlt/gateway/dltApp/tests/dltApp_Test/.gitignore new file mode 100644 index 000000000..40b878db5 --- /dev/null +++ b/src/dlt/gateway/dltApp/tests/dltApp_Test/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/src/dlt/gateway/dltApp/tests/dltApp_Test/package-lock.json b/src/dlt/gateway/dltApp/tests/dltApp_Test/package-lock.json new file mode 100644 index 000000000..c7e3f7976 --- /dev/null +++ b/src/dlt/gateway/dltApp/tests/dltApp_Test/package-lock.json @@ -0,0 +1,2060 @@ +{ + "name": "adrenalineDLT_APP", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "adrenalineDLT_APP", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.9.7", + "@hyperledger/fabric-gateway": "~1.4.0", + "dotenv": "^16.4.5" + }, + "devDependencies": { + "@tsconfig/node18": "^18.2.2", + "@types/node": "^18.18.6", + "@typescript-eslint/eslint-plugin": "^6.9.0", + "@typescript-eslint/parser": "^6.9.0", + "eslint": "^8.52.0", + "typescript": "~5.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.7.tgz", + "integrity": "sha512-ZMBVjSeDAz3tFSehyO6Pd08xZT1HfIwq3opbeM4cDlBh52gmwp0wVIPcQur53NN0ac68HMZ/7SF2rGRD5KmVmg==", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@hyperledger/fabric-gateway": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@hyperledger/fabric-gateway/-/fabric-gateway-1.4.0.tgz", + "integrity": "sha512-dJ0eJdGBo8wtZ/oR5mADHnllp+pSuVOI7uq5fRFf0NTVk1SzlX42Q3kt4j53bJQaxd21TMvofgXNO+BCgJcB/A==", + "dependencies": { + "@grpc/grpc-js": "^1.9.0", + "@hyperledger/fabric-protos": "^0.2.0", + "asn1.js": "^5.4.1", + "bn.js": "^5.2.1", + "elliptic": "^6.5.4", + "google-protobuf": "^3.21.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "optionalDependencies": { + "pkcs11js": "^1.3.0" + } + }, + "node_modules/@hyperledger/fabric-protos": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@hyperledger/fabric-protos/-/fabric-protos-0.2.1.tgz", + "integrity": "sha512-qjm0vIQIfCall804tWDeA8p/mUfu14sl5Sj+PbOn2yDKJq+7ThoIhNsLAqf+BCxUfqsoqQq6AojhqQeTFyOOqg==", + "dependencies": { + "@grpc/grpc-js": "^1.9.0", + "google-protobuf": "^3.21.0" + }, + "engines": { + "node": ">=14.15.0" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@tsconfig/node18": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.4.tgz", + "integrity": "sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.19.32", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.32.tgz", + "integrity": "sha512-2bkg93YBSDKk8DLmmHnmj/Rwr18TLx7/n+I23BigFwgexUJoMHZOd8X1OFxuF/W3NN0S2W2E5sVabI5CPinNvA==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/elliptic": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", + "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-protobuf": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz", + "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nan": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", + "optional": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkcs11js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkcs11js/-/pkcs11js-1.3.1.tgz", + "integrity": "sha512-eo7fTeQwYGzX1pFmRaf4ji/WcDW2XKpwqylOwzutsjNWECv6G9PzDHj3Yj5dX9EW/fydMnJG8xvWj/btnQT9TA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.15.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/PeculiarVentures" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/protobufjs": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", + "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/src/dlt/gateway/dltApp/tests/dltApp_Test/package.json b/src/dlt/gateway/dltApp/tests/dltApp_Test/package.json new file mode 100644 index 000000000..228dbaad9 --- /dev/null +++ b/src/dlt/gateway/dltApp/tests/dltApp_Test/package.json @@ -0,0 +1,34 @@ +{ + "name": "adrenalineDLT_APP", + "version": "1.0.0", + "description": "A DLT application that record and manages topology data as JSON implemented in typeScript using fabric-gateway", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "engines": { + "node": ">=18" + }, + "scripts": { + "build": "tsc", + "build:watch": "tsc -w", + "lint": "eslint . --ext .ts", + "prepare": "npm run build", + "pretest": "npm run lint", + "start": "node dist/adrenalineDLT_app.js" + }, + "engineStrict": true, + "author": "CTTC-Javier", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.9.7", + "@hyperledger/fabric-gateway": "~1.4.0", + "dotenv": "^16.4.5" + }, + "devDependencies": { + "@tsconfig/node18": "^18.2.2", + "@types/node": "^18.18.6", + "@typescript-eslint/eslint-plugin": "^6.9.0", + "@typescript-eslint/parser": "^6.9.0", + "eslint": "^8.52.0", + "typescript": "~5.2.2" + } +} diff --git a/src/dlt/gateway/dltApp/tests/dltApp_Test/src/adrenalineDLT_app.ts b/src/dlt/gateway/dltApp/tests/dltApp_Test/src/adrenalineDLT_app.ts new file mode 100644 index 000000000..d1cd3a5e3 --- /dev/null +++ b/src/dlt/gateway/dltApp/tests/dltApp_Test/src/adrenalineDLT_app.ts @@ -0,0 +1,341 @@ +/* + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as grpc from '@grpc/grpc-js'; +import { connect, Contract, Identity, Signer, signers, Network, CloseableAsyncIterable, ChaincodeEvent, GatewayError } from '@hyperledger/fabric-gateway'; +import * as crypto from 'crypto'; +import { promises as fs } from 'fs'; +import * as path from 'path'; +import { TextDecoder } from 'util'; +import * as dotenv from 'dotenv'; + +dotenv.config(); +const channelName = envOrDefault('CHANNEL_NAME', 'channel1'); +const chaincodeName = envOrDefault('CHAINCODE_NAME', 'adrenalineDLT'); +const mspId = envOrDefault('MSP_ID', 'Org1MSP'); + +// Path to crypto materials. +const cryptoPath = envOrDefault('CRYPTO_PATH', path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.adrenaline.com')); + +// Path to user private key directory. +const keyDirectoryPath = envOrDefault('KEY_DIRECTORY_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.adrenaline.com', 'msp', 'keystore')); + +// Path to user certificate directory. +const certDirectoryPath = envOrDefault('CERT_DIRECTORY_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.adrenaline.com', 'msp', 'signcerts')); + +// Path to peer tls certificate. +const tlsCertPath = envOrDefault('TLS_CERT_PATH', path.resolve(cryptoPath, 'peers', 'peer0.org1.adrenaline.com', 'tls', 'ca.crt')); + +// Gateway peer endpoint. +const peerEndpoint = envOrDefault('PEER_ENDPOINT', 'localhost:7051'); + +// Gateway peer SSL host name override. +const peerHostAlias = envOrDefault('PEER_HOST_ALIAS', 'peer0.org1.adrenaline.com'); + +const utf8Decoder = new TextDecoder(); +const assetId = `asset${Date.now()}`; + +async function main(): Promise { + + await displayInputParameters(); + + // The gRPC client connection should be shared by all Gateway connections to this endpoint. + const client = await newGrpcConnection(); + + const gateway = connect({ + client, + identity: await newIdentity(), + signer: await newSigner(), + // Default timeouts for different gRPC calls + evaluateOptions: () => { + return { deadline: Date.now() + 5000 }; // 5 seconds + }, + endorseOptions: () => { + return { deadline: Date.now() + 15000 }; // 15 seconds + }, + submitOptions: () => { + return { deadline: Date.now() + 5000 }; // 5 seconds + }, + commitStatusOptions: () => { + return { deadline: Date.now() + 60000 }; // 1 minute + }, + }); + + let events: CloseableAsyncIterable | undefined; + + try { + // Get a network instance representing the channel where the smart contract is deployed. + const network = gateway.getNetwork(channelName); + + // Get the smart contract from the network. + const contract = network.getContract(chaincodeName); + + //Listen for events emitted by transactions + events = await startEventListening(network); + + // Initialize the ledger. + await initLedger(contract); + +// Create a new asset on the ledger and gets the blocknumber. + const firstBlockNumber = await StoreTopoData(contract); + + // Get the asset details by key. + await RetrieveTopoData(contract); + + //Updates existing record of a topology + await updateRecord(contract); + + // Verifies the changes. + await RetrieveTopoData(contract); + + //Deletes existing topology + await deleteRecordByID(contract); + + // Return all the current assets on the ledger. + await GetAllInfo(contract); + + // Update an asset which does not exist. + await updateNonExistentRecord(contract) + + // Replay events from the block containing the first transactions + await replayChaincodeEvents(network, firstBlockNumber); + + } finally { + events?.close(); + gateway.close(); + client.close(); + } +} + +main().catch(error => { + console.error('******** FAILED to run the application:', error); + process.exitCode = 1; +}); + +async function newGrpcConnection(): Promise { + const tlsRootCert = await fs.readFile(tlsCertPath); + const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); + return new grpc.Client(peerEndpoint, tlsCredentials, { + 'grpc.ssl_target_name_override': peerHostAlias, + }); +} + +async function newIdentity(): Promise { + const certPath = await getFirstDirFileName(certDirectoryPath); + const credentials = await fs.readFile(certPath); + return { mspId, credentials }; +} + +async function getFirstDirFileName(dirPath: string): Promise { + const files = await fs.readdir(dirPath); + return path.join(dirPath, files[0]); +} + +async function newSigner(): Promise { + const keyPath = await getFirstDirFileName(keyDirectoryPath); + const privateKeyPem = await fs.readFile(keyPath); + const privateKey = crypto.createPrivateKey(privateKeyPem); + return signers.newPrivateKeySigner(privateKey); +} + +/** + * This type of transaction would typically only be run once by an application the first time it was started after its + * initial deployment. A new version of the chaincode deployed later would likely not need to run an "init" function. + */ +async function initLedger(contract: Contract): Promise { + try { + console.log('\n--> Submit Transaction: InitLedger, function activates the chaincode'); + + await contract.submitTransaction('InitLedger'); + + console.log('*** Transaction committed successfully'); + } catch (error) { + console.error('Failed to submit InitLedger transaction:', error); + throw error; + } +} + + + +/** + * Evaluate a transaction to query ledger state. + */ +async function GetAllInfo(contract: Contract): Promise { + console.log('\n--> Evaluate Transaction: GetAllInfo, function returns all the current topologies on the ledger'); + + const resultBytes = await contract.evaluateTransaction('GetAllInfo'); + + const resultJson = utf8Decoder.decode(resultBytes); + const result = JSON.parse(resultJson); + console.log('*** Result:', result); +} + +/** + * Submit a transaction Asynchronously. + */ +async function StoreTopoData(contract: Contract): Promise { + console.log('\n--> Submit Transaction: StoreTopoData, records a new topology structure by Key values'); + + const storeTopoDataPath = path.resolve(__dirname, '..', '..', '..', 'samples', 'sampleTopo.json'); + const jsonData = await fs.readFile(storeTopoDataPath, 'utf8'); + const result = await contract.submitAsync('StoreTopoData', { + arguments: [assetId, jsonData] + }); + + const status = await result.getStatus(); + if (!status.successful) { + throw new Error(`failed to commit transaction ${status.transactionId} with status code ${status.code}`); + } + + console.log('*** Transaction committed successfully'); + return status.blockNumber; +} + +/** + * updateRecord(), updates topology record by key synchronously. + */ +async function updateRecord(contract: Contract): Promise { + console.log(`\n--> Submit transaction: UpdateTopoData, ${assetId}`); + + + try { + const updateTopoDataPath = path.resolve(__dirname, '..', '..', '..', 'samples', 'updatedTopo.json'); + const jsonData = await fs.readFile(updateTopoDataPath, 'utf8'); + await contract.submitTransaction('UpdateTopoData', assetId, jsonData); + console.log('UpdateTopoData committed successfully'); + } catch (error) { + console.log('*** Successfully caught the error: \n', error); + } +} + +/** + * RetrieveTopoData(), gets the topology information by key. + */ +async function RetrieveTopoData(contract: Contract): Promise { + console.log('\n--> Evaluate Transaction: RetrieveTopoData, function returns topology attributes'); + + const resultBytes = await contract.evaluateTransaction('RetrieveTopoData', assetId); + + const resultJson = utf8Decoder.decode(resultBytes); + const result = JSON.parse(resultJson); + console.log('*** Result:', result); +} + +/** + * submitTransaction() will throw an error containing details of any error responses from the smart contract. + */ +async function updateNonExistentRecord(contract: Contract): Promise{ + console.log('\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error'); + + try { + const updateTopoDataPath = path.resolve(__dirname, '..', '..', '..', 'samples', 'updatedTopo.json'); + const jsonData = await fs.readFile(updateTopoDataPath, 'utf8'); + await contract.submitTransaction('UpdateTopoData', 'nonExist', jsonData) + console.log('******** FAILED to return an error'); + } catch (error) { + console.log('*** Successfully caught the error: \n', error); + } +} + +/** + * deleteRecordByID() removes a record from the ledger. + */ + +async function deleteRecordByID(contract: Contract): Promise{ + console.log(`\n--> Submit transaction: deleteRecordByID, ${assetId}`); + try { + + await contract.submitTransaction('DeleteTopo', assetId); + console.log('\n*** deleteRecordByID committed successfully'); + } catch (error) { + console.log('*** Successfully caught the error: \n', error); + } +} + + +/** + * envOrDefault() will return the value of an environment variable, or a default value if the variable is undefined. + */ +function envOrDefault(key: string, defaultValue: string): string { + return process.env[key] || defaultValue; +} + +/** + * displayInputParameters() will print the global scope parameters used by the main driver routine. + */ +async function displayInputParameters(): Promise { + console.log(`channelName: ${channelName}`); + console.log(`chaincodeName: ${chaincodeName}`); + console.log(`mspId: ${mspId}`); + console.log(`cryptoPath: ${cryptoPath}`); + console.log(`keyDirectoryPath: ${keyDirectoryPath}`); + console.log(`certDirectoryPath: ${certDirectoryPath}`); + console.log(`tlsCertPath: ${tlsCertPath}`); + console.log(`peerEndpoint: ${peerEndpoint}`); + console.log(`peerHostAlias: ${peerHostAlias}`); +} + +/** + * startEventListening() will initiate the event listener for chaincode events. + */ +async function startEventListening(network: Network): Promise> { + console.log('\n*** Start chaincode event listening'); + + const events = await network.getChaincodeEvents(chaincodeName); + + void readEvents(events); // Don't await - run asynchronously + return events; +} + +/** + * readEvents() format and display the events as a JSON. + */ +async function readEvents(events: CloseableAsyncIterable): Promise { + try { + for await (const event of events) { + const payload = parseJson(event.payload); + console.log(`\n<-- Chaincode event received: ${event.eventName} -`, payload); + } + } catch (error: unknown) { + // Ignore the read error when events.close() is called explicitly + if (!(error instanceof GatewayError) || error.code !== grpc.status.CANCELLED.valueOf()) { + throw error; + } + } +} + +/** + * parseJson() formats a JSON. + */ +function parseJson(jsonBytes: Uint8Array): unknown { + const json = utf8Decoder.decode(jsonBytes); + return JSON.parse(json); +} + + +/** + * replayChaincodeEvents() + */ +async function replayChaincodeEvents(network: Network, startBlock: bigint): Promise { + console.log('\n*** Start chaincode event replay'); + + const events = await network.getChaincodeEvents(chaincodeName, { + startBlock, + }); + + try { + for await (const event of events) { + const payload = parseJson(event.payload); + console.log(`\n<-- Chaincode event replayed: ${event.eventName} -`, payload); + + if (event.eventName === 'DeleteTopo') { + // Reached the last submitted transaction so break to stop listening for events + break; + } + } + } finally { + events.close(); + } +} \ No newline at end of file diff --git a/src/dlt/gateway/dltApp/tests/dltApp_Test/tsconfig.json b/src/dlt/gateway/dltApp/tests/dltApp_Test/tsconfig.json new file mode 100644 index 000000000..ed2150983 --- /dev/null +++ b/src/dlt/gateway/dltApp/tests/dltApp_Test/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends":"@tsconfig/node18/tsconfig.json", + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "outDir": "dist", + "declaration": true, + "sourceMap": true, + "noImplicitAny": true + }, + "include": [ + "./src/**/*" + ], + "exclude": [ + "./src/**/*.spec.ts" + ] + } + \ No newline at end of file diff --git a/src/dlt/gateway/dltApp/tests/perfTest.js b/src/dlt/gateway/dltApp/tests/perfTest.js new file mode 100644 index 000000000..d2892d451 --- /dev/null +++ b/src/dlt/gateway/dltApp/tests/perfTest.js @@ -0,0 +1,105 @@ +const { connectToNetwork } = require('../dist/fabricConnect'); +const fsp = require('fs').promises; +const fs = require('fs'); +const util = require('util'); + +const utf8Decoder = new TextDecoder(); +const topoDirectory = '../samples/'; +//const topologies = ['topo1.json', 'topo2.json', 'topo3.json', 'topo4.json']; +const topologies = ['topo4.json']; + +const iterations = 1000; + +async function main() { + try { + const { contract, close } = await connectToNetwork(); + for (const topoFile of topologies) { + const logFilePath = `./operation_times_${topoFile.split('.')[0]}.txt`; // Creates a separate logfile for each topology + const appendFile = util.promisify(fs.appendFile.bind(fs, logFilePath)); + + console.log(`Starting tests for ${topoFile}`); + for (let i = 0; i < iterations; i++) { + console.log(`Iteration ${i + 1} for ${topoFile}`); + await runBlockchainOperations(contract, topoFile, appendFile); + } + } + await close(); // Clean up the connection + } catch (error) { + console.error('An error occurred:', error); + } +} + +async function runBlockchainOperations(contract, topoFile, appendFile) { + const assetId = `asset${Date.now()}`; + const jsonData = await readJsonData(`${topoDirectory}${topoFile}`); + + // Define operations + const operations = [ + { type: 'STORE', jsonData }, + { type: 'UPDATE', jsonData }, + { type: 'FETCH', jsonData: null }, + { type: 'DELETE', jsonData: null }, + { type: 'FETCH_ALL', jsonData: null } + ]; + + for (let op of operations) { + await executeOperation(contract, op.type, assetId, op.jsonData, appendFile); + } +} + +async function readJsonData(filePath) { + try { + return await fsp.readFile(filePath, 'utf8'); + } catch (error) { + console.error(`Failed to read file: ${filePath}`, error); + return '{}'; + } +} + +async function executeOperation(contract, operationType, assetId, jsonData, appendFile) { + const startTime = process.hrtime.bigint(); + try { + let result; + switch (operationType) { + case 'STORE': + result = await contract.submitTransaction('StoreTopoData', assetId, jsonData); + break; + case 'UPDATE': + result = await contract.submitTransaction('UpdateTopoData', assetId, jsonData); + break; + case 'FETCH': + result = await contract.evaluateTransaction('RetrieveTopoData', assetId); + break; + case 'DELETE': + result = await contract.submitTransaction('DeleteTopo', assetId); + break; + case 'FETCH_ALL': + result = await contract.evaluateTransaction('GetAllInfo'); + break; + } + result = utf8Decoder.decode(result); + const operationTime = recordOperationTime(startTime); + await logOperationTime(operationTime, operationType, appendFile); + console.log(`${operationType} Result:`, result); + } catch (error) { + console.error(`Error during ${operationType}:`, error); + } +} + +function recordOperationTime(startTime) { + const endTime = process.hrtime.bigint(); + const operationTime = Number(endTime - startTime) / 1e6; + return operationTime; +} + +async function logOperationTime(operationTime, operationType, appendFile) { + const timestamp = Date.now(); + const logEntry = `${timestamp} - ${operationType} - Execution time: ${operationTime.toFixed(3)} ms\n`; + try { + await appendFile(logEntry); + } catch (error) { + console, error('Error writing to log file:', error); + } +} + +main(); diff --git a/src/dlt/gateway/dltApp/tests/rateTest.js b/src/dlt/gateway/dltApp/tests/rateTest.js new file mode 100644 index 000000000..95370f487 --- /dev/null +++ b/src/dlt/gateway/dltApp/tests/rateTest.js @@ -0,0 +1,71 @@ +const { connectToNetwork } = require('../dist/fabricConnect'); +const fs = require('fs'); +const util = require('util'); +const appendFile = util.promisify(fs.appendFile); +const logFilePath = './transaction_times_TPS_TOPO3.txt'; +const utf8Decoder = new TextDecoder(); +const topoDirectory = '../samples/'; + +async function main() { + const { contract, close } = await connectToNetwork(); + try { + const rates = [10, 50, 250, 500]; // Transactions per second + for (let i = 0; i < 1000; i++) { + for (let rate of rates) { + console.log(`Testing at ${rate} TPS`); + await performLoadTest(contract, 1000, rate); + } + } + } finally { + await close(); // Ensure to close the network connection + } +} + +async function performLoadTest(contract, totalTransactions, rate) { + const interval = 1000 / rate; // Calculate interval in milliseconds + let promises = []; + const startTime = Date.now(); + + for (let i = 0; i < totalTransactions; i++) { + // Queue a transaction promise + promises.push(sendTransaction(contract, `asset${Date.now() + i}`)); + + // Process in batches according to the rate + if ((i + 1) % rate === 0 || i === totalTransactions - 1) { + await Promise.all(promises); // Send a batch of transactions + promises = []; // Reset for the next batch + if (i < totalTransactions - 1) { + await new Promise(resolve => setTimeout(resolve, interval)); // Throttle the transaction sending + } + } + } + + const endTime = Date.now(); + const totalTime = endTime - startTime; + const actualRate = totalTransactions / (totalTime / 1000); + console.log(`Total time for ${totalTransactions} transactions at target ${rate} TPS: ${totalTime} ms`); + console.log(`Actual rate achieved: ${actualRate.toFixed(2)} TPS`); + await appendFile(logFilePath, `Target Rate: ${rate} TPS, Total Time: ${totalTime} ms, Actual Rate: ${actualRate.toFixed(2)} TPS\n`); +} + +async function sendTransaction(contract, assetId) { + try { + const jsonData = await readJsonData(`${topoDirectory}topo3.json`); + const result = await contract.submitTransaction('StoreTopoData', assetId, jsonData); + return utf8Decoder.decode(result); + } catch (error) { + console.error('Transaction failed:', error); + return null; + } +} + +async function readJsonData(filePath) { + try { + return await fs.promises.readFile(filePath, 'utf8'); + } catch (error) { + console.error(`Failed to read file: ${filePath}`, error); + return '{}'; + } +} + +main().catch(console.error); diff --git a/src/dlt/gateway/dltApp/tests/simpleTest.js b/src/dlt/gateway/dltApp/tests/simpleTest.js new file mode 100644 index 000000000..98c6ae135 --- /dev/null +++ b/src/dlt/gateway/dltApp/tests/simpleTest.js @@ -0,0 +1,60 @@ +const { connectToNetwork } = require('../dist/fabricConnect'); +const fs = require('fs'); +const util = require('util'); +const appendFile = util.promisify(fs.appendFile); +const logFilePath = './transaction_times_TPS2.txt'; +const utf8Decoder = new TextDecoder(); +const topoDirectory = '../samples/'; + +async function main() { + const { contract, close } = await connectToNetwork(); + try { + console.log('Testing with 8 consecutive transactions'); + await performLoadTest(contract); + } finally { + await close(); // Ensure to close the network connection + } +} + +async function performLoadTest(contract) { + const totalTransactions = 500; + const promises = []; + const startTime = Date.now(); + + for (let i = 0; i < totalTransactions; i++) { + // Queue a transaction promise + promises.push(sendTransaction(contract, `asset${startTime}_${i}`)); + } + + await Promise.all(promises); // Send all transactions + + const endTime = Date.now(); + const totalTime = endTime - startTime; + const actualRate = totalTransactions / (totalTime / 1000); + console.log(`Total time for ${totalTransactions} transactions: ${totalTime} ms`); + console.log(`Actual rate achieved: ${actualRate.toFixed(2)} TPS`); + await appendFile(logFilePath, `Total Transactions: ${totalTransactions}, Total Time: ${totalTime} ms, Actual Rate: ${actualRate.toFixed(2)} TPS\n`); +} + +async function sendTransaction(contract, assetId) { + try { + const jsonData = await readJsonData(`${topoDirectory}topo4.json`); + //const jsonData = JSON.stringify({ data: `Data for ${assetId}`}); + const result = await contract.submitTransaction('StoreTopoData', assetId, jsonData); + return utf8Decoder.decode(result); + } catch (error) { + console.error('Transaction failed:', error); + return null; + } +} + +async function readJsonData(filePath) { + try { + return await fs.promises.readFile(filePath, 'utf8'); + } catch (error) { + console.error(`Failed to read file: ${filePath}`, error); + return '{}'; + } +} + +main().catch(console.error); diff --git a/src/dlt/gateway/dltApp/tsconfig.json b/src/dlt/gateway/dltApp/tsconfig.json new file mode 100644 index 000000000..56f092a36 --- /dev/null +++ b/src/dlt/gateway/dltApp/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends":"@tsconfig/node18/tsconfig.json", + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "outDir": "dist", + "declaration": true, + "sourceMap": true, + "noImplicitAny": true + }, + "include": [ + "./src/**/*" +, "src.old/grpcClient.js", "src.old/grpcServer.js" ], + "exclude": [ + "./src/**/*.spec.ts" + ] + } + \ No newline at end of file diff --git a/src/dlt/gateway/gradle.properties b/src/dlt/gateway/gradle.properties deleted file mode 100644 index 7fc6f1ff2..000000000 --- a/src/dlt/gateway/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -kotlin.code.style=official diff --git a/src/dlt/gateway/gradle/wrapper/gradle-wrapper.jar b/src/dlt/gateway/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL diff --git a/src/dlt/gateway/gradle/wrapper/gradle-wrapper.properties b/src/dlt/gateway/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 69a971507..000000000 --- a/src/dlt/gateway/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/src/dlt/gateway/gradlew b/src/dlt/gateway/gradlew deleted file mode 100755 index 744e882ed..000000000 --- a/src/dlt/gateway/gradlew +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -exec "$JAVACMD" "$@" diff --git a/src/dlt/gateway/gradlew.bat b/src/dlt/gateway/gradlew.bat deleted file mode 100644 index ac1b06f93..000000000 --- a/src/dlt/gateway/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/src/dlt/gateway/samples/sampleTopo.json b/src/dlt/gateway/samples/sampleTopo.json new file mode 100644 index 000000000..435d0bfbe --- /dev/null +++ b/src/dlt/gateway/samples/sampleTopo.json @@ -0,0 +1,30 @@ +{ + "name": "Network A", + "nodes": [ + { + "id": "node1", + "type": "switch", + "status": "active", + "connections": [ + "node2", + "node3" + ] + }, + { + "id": "node2", + "type": "router", + "status": "inactive", + "connections": [ + "node1" + ] + }, + { + "id": "node3", + "type": "server", + "status": "active", + "connections": [ + "node1" + ] + } + ] +} \ No newline at end of file diff --git a/src/dlt/gateway/samples/topo1.json b/src/dlt/gateway/samples/topo1.json new file mode 100644 index 000000000..e4a49981f --- /dev/null +++ b/src/dlt/gateway/samples/topo1.json @@ -0,0 +1,96 @@ +{ + "contexts": [ + {"context_id": {"context_uuid": {"uuid": "admin"}}} + ], + "topologies": [ + {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}} + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0], + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "eth1", "type": "copper"}, {"uuid": "eth2", "type": "copper"}, {"uuid": "int", "type": "copper"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0], + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "eth1", "type": "copper"}, {"uuid": "eth2", "type": "copper"}, {"uuid": "int", "type": "copper"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "SRL1"}}, "device_type": "packet-router", "device_drivers": [8], + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.100.100.101"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "57400"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "NokiaSrl1!", "use_tls": true + }}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "SRL2"}}, "device_type": "packet-router", "device_drivers": [8], + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.100.100.102"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "57400"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "NokiaSrl1!", "use_tls": true + }}} + ]} + } + ], + "links": [ + { + "link_id": {"link_uuid": {"uuid": "DC1/eth1==SRL1/ethernet-1/2"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}, + {"device_id": {"device_uuid": {"uuid": "SRL1"}}, "endpoint_uuid": {"uuid": "ethernet-1/2"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "SRL1/ethernet-1/2==DC1/eth1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "SRL1"}}, "endpoint_uuid": {"uuid": "ethernet-1/2"}}, + {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}} + ] + }, + + { + "link_id": {"link_uuid": {"uuid": "SRL1/ethernet-1/1==SRL2/ethernet-1/1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "SRL1"}}, "endpoint_uuid": {"uuid": "ethernet-1/1"}}, + {"device_id": {"device_uuid": {"uuid": "SRL2"}}, "endpoint_uuid": {"uuid": "ethernet-1/1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "SRL2/ethernet-1/1==SRL1/ethernet-1/1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "SRL2"}}, "endpoint_uuid": {"uuid": "ethernet-1/1"}}, + {"device_id": {"device_uuid": {"uuid": "SRL1"}}, "endpoint_uuid": {"uuid": "ethernet-1/1"}} + ] + }, + + { + "link_id": {"link_uuid": {"uuid": "DC2/eth1==SRL2/ethernet-1/2"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}, + {"device_id": {"device_uuid": {"uuid": "SRL2"}}, "endpoint_uuid": {"uuid": "ethernet-1/2"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "SRL2/ethernet-1/2==DC2/eth1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "SRL2"}}, "endpoint_uuid": {"uuid": "ethernet-1/2"}}, + {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}} + ] + } + ] +} diff --git a/src/dlt/gateway/samples/topo2.json b/src/dlt/gateway/samples/topo2.json new file mode 100644 index 000000000..6885c7d90 --- /dev/null +++ b/src/dlt/gateway/samples/topo2.json @@ -0,0 +1,210 @@ +{ + "contexts": [ + {"context_id": {"context_uuid": {"uuid": "admin"}}} + ], + "topologies": [ + {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}} + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/3"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/4"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/5"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/6"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/3"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/3"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/4"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/5"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/6"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/3"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/3"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/4"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/5"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/6"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/3"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R4"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/3"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/4"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/5"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/6"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/3"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R5"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/3"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/4"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/5"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/6"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/3"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R6"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/3"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/4"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/5"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/6"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/3"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R7"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "1/3"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/1"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/2"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/3"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/4"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/5"}, + {"sample_types": [101, 102, 201, 202], "type": "copper", "uuid": "2/6"} + ]}}} + ]} + } + ], + "links": [ + {"link_id": {"link_uuid": {"uuid": "R1==R2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "2/1"}}, + {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "2/2"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R1==R6"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "2/2"}}, + {"device_id": {"device_uuid": {"uuid": "R6"}}, "endpoint_uuid": {"uuid": "2/1"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R1==R7"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "2/3"}}, + {"device_id": {"device_uuid": {"uuid": "R7"}}, "endpoint_uuid": {"uuid": "2/1"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R2==R1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "2/2"}}, + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "2/1"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R2==R3"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "2/1"}}, + {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "2/2"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R3==R2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "2/2"}}, + {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "2/1"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R3==R4"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "2/1"}}, + {"device_id": {"device_uuid": {"uuid": "R4"}}, "endpoint_uuid": {"uuid": "2/2"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R3==R7"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "2/3"}}, + {"device_id": {"device_uuid": {"uuid": "R7"}}, "endpoint_uuid": {"uuid": "2/3"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R4==R3"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R4"}}, "endpoint_uuid": {"uuid": "2/2"}}, + {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "2/1"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R4==R5"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R4"}}, "endpoint_uuid": {"uuid": "2/1"}}, + {"device_id": {"device_uuid": {"uuid": "R5"}}, "endpoint_uuid": {"uuid": "2/2"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R5==R4"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R5"}}, "endpoint_uuid": {"uuid": "2/2"}}, + {"device_id": {"device_uuid": {"uuid": "R4"}}, "endpoint_uuid": {"uuid": "2/1"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R5==R6"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R5"}}, "endpoint_uuid": {"uuid": "2/1"}}, + {"device_id": {"device_uuid": {"uuid": "R6"}}, "endpoint_uuid": {"uuid": "2/2"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R5==R7"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R5"}}, "endpoint_uuid": {"uuid": "2/3"}}, + {"device_id": {"device_uuid": {"uuid": "R7"}}, "endpoint_uuid": {"uuid": "2/5"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R6==R1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R6"}}, "endpoint_uuid": {"uuid": "2/1"}}, + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "2/2"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R6==R5"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R6"}}, "endpoint_uuid": {"uuid": "2/2"}}, + {"device_id": {"device_uuid": {"uuid": "R5"}}, "endpoint_uuid": {"uuid": "2/1"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R7==R1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R7"}}, "endpoint_uuid": {"uuid": "2/1"}}, + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "2/3"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R7==R3"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R7"}}, "endpoint_uuid": {"uuid": "2/3"}}, + {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "2/3"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R7==R5"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R7"}}, "endpoint_uuid": {"uuid": "2/5"}}, + {"device_id": {"device_uuid": {"uuid": "R5"}}, "endpoint_uuid": {"uuid": "2/3"}} + ]} + ] +} diff --git a/src/dlt/gateway/samples/topo3.json b/src/dlt/gateway/samples/topo3.json new file mode 100644 index 000000000..f36fbd7d0 --- /dev/null +++ b/src/dlt/gateway/samples/topo3.json @@ -0,0 +1,200 @@ +{ + "contexts": [ + {"context_id": {"context_uuid": {"uuid": "admin"}}, "name": "admin"} + ], + "topologies": [ + {"topology_id": {"topology_uuid": {"uuid": "admin"}, "context_id": {"context_uuid": {"uuid": "admin"}}}, "name": "admin"} + ], + "devices": [ + {"device_id": {"device_uuid": {"uuid": "fr1.fr"}}, "device_type": "emu-packet-router", "device_operational_status": 1, "device_drivers": [0], "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "client:1", "type": "copper"}, {"uuid": "client:2", "type": "copper"}, {"uuid": "client:3", "type": "copper"}, + {"uuid": "be1.be", "type": "copper"}, {"uuid": "pt1.pt", "type": "copper"}, {"uuid": "uk1.uk", "type": "copper"}, + {"uuid": "es1.es", "type": "copper"}, {"uuid": "it1.it", "type": "copper"} + ]}}} + ]}}, + {"device_id": {"device_uuid": {"uuid": "be1.be"}}, "device_type": "emu-packet-router", "device_operational_status": 1, "device_drivers": [0], "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "client:1", "type": "copper"}, {"uuid": "client:2", "type": "copper"}, {"uuid": "client:3", "type": "copper"}, + {"uuid": "de1.de", "type": "copper"}, {"uuid": "gr1.gr", "type": "copper"}, {"uuid": "uk1.uk", "type": "copper"}, + {"uuid": "fr1.fr", "type": "copper"}, {"uuid": "it1.it", "type": "copper"} + ]}}} + ]}}, + {"device_id": {"device_uuid": {"uuid": "uk1.uk"}}, "device_type": "emu-packet-router", "device_operational_status": 1, "device_drivers": [0], "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "client:1", "type": "copper"}, {"uuid": "client:2", "type": "copper"}, {"uuid": "client:3", "type": "copper"}, + {"uuid": "de1.de", "type": "copper"}, {"uuid": "fr1.fr", "type": "copper"}, {"uuid": "be1.be", "type": "copper"}, + {"uuid": "pt1.pt", "type": "copper"} + ]}}} + ]}}, + {"device_id": {"device_uuid": {"uuid": "de1.de"}}, "device_type": "emu-packet-router", "device_operational_status": 1, "device_drivers": [0], "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "client:1", "type": "copper"}, {"uuid": "client:2", "type": "copper"}, {"uuid": "client:3", "type": "copper"}, + {"uuid": "uk1.uk", "type": "copper"}, {"uuid": "be1.be", "type": "copper"}, {"uuid": "gr1.gr", "type": "copper"} + ]}}} + ]}}, + {"device_id": {"device_uuid": {"uuid": "pt1.pt"}}, "device_type": "emu-packet-router", "device_operational_status": 1, "device_drivers": [0], "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "client:1", "type": "copper"}, {"uuid": "client:2", "type": "copper"}, {"uuid": "client:3", "type": "copper"}, + {"uuid": "uk1.uk", "type": "copper"}, {"uuid": "fr1.fr", "type": "copper"}, {"uuid": "es1.es", "type": "copper"} + ]}}} + ]}}, + {"device_id": {"device_uuid": {"uuid": "es1.es"}}, "device_type": "emu-packet-router", "device_operational_status": 1, "device_drivers": [0], "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "client:1", "type": "copper"}, {"uuid": "client:2", "type": "copper"}, {"uuid": "client:3", "type": "copper"}, + {"uuid": "it1.it", "type": "copper"}, {"uuid": "fr1.fr", "type": "copper"}, {"uuid": "pt1.pt", "type": "copper"} + ]}}} + ]}}, + {"device_id": {"device_uuid": {"uuid": "it1.it"}}, "device_type": "emu-packet-router", "device_operational_status": 1, "device_drivers": [0], "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "client:1", "type": "copper"}, {"uuid": "client:2", "type": "copper"}, {"uuid": "client:3", "type": "copper"}, + {"uuid": "es1.es", "type": "copper"}, {"uuid": "fr1.fr", "type": "copper"}, {"uuid": "be1.be", "type": "copper"}, + {"uuid": "gr1.gr", "type": "copper"} + ]}}} + ]}}, + {"device_id": {"device_uuid": {"uuid": "gr1.gr"}}, "device_type": "emu-packet-router", "device_operational_status": 1, "device_drivers": [0], "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "client:1", "type": "copper"}, {"uuid": "client:2", "type": "copper"}, {"uuid": "client:3", "type": "copper"}, + {"uuid": "it1.it", "type": "copper"}, {"uuid": "de1.de", "type": "copper"}, {"uuid": "be1.be", "type": "copper"} + ]}}} + ]}} + ], + "links": [ + {"link_id": {"link_uuid": {"uuid": "fr1.fr_be1.be"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 4.804849}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "fr1.fr"}}, "endpoint_uuid": {"uuid": "be1.be"}}, + {"device_id": {"device_uuid": {"uuid": "be1.be"}}, "endpoint_uuid": {"uuid": "fr1.fr"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "uk1.uk_fr1.fr"}}, "attributes": {"total_capacity_gbps": 300, "used_capacity_gbps": 55.182499}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "uk1.uk"}}, "endpoint_uuid": {"uuid": "fr1.fr"}}, + {"device_id": {"device_uuid": {"uuid": "fr1.fr"}}, "endpoint_uuid": {"uuid": "uk1.uk"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "uk1.uk_de1.de"}}, "attributes": {"total_capacity_gbps": 600, "used_capacity_gbps": 199.272255}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "uk1.uk"}}, "endpoint_uuid": {"uuid": "de1.de"}}, + {"device_id": {"device_uuid": {"uuid": "de1.de"}}, "endpoint_uuid": {"uuid": "uk1.uk"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "uk1.uk_be1.be"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 14.334868}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "uk1.uk"}}, "endpoint_uuid": {"uuid": "be1.be"}}, + {"device_id": {"device_uuid": {"uuid": "be1.be"}}, "endpoint_uuid": {"uuid": "uk1.uk"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "pt1.pt_uk1.uk"}}, "attributes": {"total_capacity_gbps": 400, "used_capacity_gbps": 51.415678}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "pt1.pt"}}, "endpoint_uuid": {"uuid": "uk1.uk"}}, + {"device_id": {"device_uuid": {"uuid": "uk1.uk"}}, "endpoint_uuid": {"uuid": "pt1.pt"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "pt1.pt_fr1.fr"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 3.733925}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "pt1.pt"}}, "endpoint_uuid": {"uuid": "fr1.fr"}}, + {"device_id": {"device_uuid": {"uuid": "fr1.fr"}}, "endpoint_uuid": {"uuid": "pt1.pt"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "pt1.pt_es1.es"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 13.32428}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "pt1.pt"}}, "endpoint_uuid": {"uuid": "es1.es"}}, + {"device_id": {"device_uuid": {"uuid": "es1.es"}}, "endpoint_uuid": {"uuid": "pt1.pt"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "it1.it_gr1.gr"}}, "attributes": {"total_capacity_gbps": 800, "used_capacity_gbps": 1.593313}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "it1.it"}}, "endpoint_uuid": {"uuid": "gr1.gr"}}, + {"device_id": {"device_uuid": {"uuid": "gr1.gr"}}, "endpoint_uuid": {"uuid": "it1.it"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "it1.it_fr1.fr"}}, "attributes": {"total_capacity_gbps": 200, "used_capacity_gbps": 98.574706}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "it1.it"}}, "endpoint_uuid": {"uuid": "fr1.fr"}}, + {"device_id": {"device_uuid": {"uuid": "fr1.fr"}}, "endpoint_uuid": {"uuid": "it1.it"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "it1.it_es1.es"}}, "attributes": {"total_capacity_gbps": 300, "used_capacity_gbps": 18.97108}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "it1.it"}}, "endpoint_uuid": {"uuid": "es1.es"}}, + {"device_id": {"device_uuid": {"uuid": "es1.es"}}, "endpoint_uuid": {"uuid": "it1.it"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "it1.it_be1.be"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 10.327772}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "it1.it"}}, "endpoint_uuid": {"uuid": "be1.be"}}, + {"device_id": {"device_uuid": {"uuid": "be1.be"}}, "endpoint_uuid": {"uuid": "it1.it"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "gr1.gr_it1.it"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 7.983659}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "gr1.gr"}}, "endpoint_uuid": {"uuid": "it1.it"}}, + {"device_id": {"device_uuid": {"uuid": "it1.it"}}, "endpoint_uuid": {"uuid": "gr1.gr"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "gr1.gr_de1.de"}}, "attributes": {"total_capacity_gbps": 5000, "used_capacity_gbps": 4930.897339}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "gr1.gr"}}, "endpoint_uuid": {"uuid": "de1.de"}}, + {"device_id": {"device_uuid": {"uuid": "de1.de"}}, "endpoint_uuid": {"uuid": "gr1.gr"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "gr1.gr_be1.be"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 0.895539}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "gr1.gr"}}, "endpoint_uuid": {"uuid": "be1.be"}}, + {"device_id": {"device_uuid": {"uuid": "be1.be"}}, "endpoint_uuid": {"uuid": "gr1.gr"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "fr1.fr_uk1.uk"}}, "attributes": {"total_capacity_gbps": 200, "used_capacity_gbps": 28.144199}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "fr1.fr"}}, "endpoint_uuid": {"uuid": "uk1.uk"}}, + {"device_id": {"device_uuid": {"uuid": "uk1.uk"}}, "endpoint_uuid": {"uuid": "fr1.fr"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "fr1.fr_pt1.pt"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 1.916587}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "fr1.fr"}}, "endpoint_uuid": {"uuid": "pt1.pt"}}, + {"device_id": {"device_uuid": {"uuid": "pt1.pt"}}, "endpoint_uuid": {"uuid": "fr1.fr"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "fr1.fr_it1.it"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 3.330747}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "fr1.fr"}}, "endpoint_uuid": {"uuid": "it1.it"}}, + {"device_id": {"device_uuid": {"uuid": "it1.it"}}, "endpoint_uuid": {"uuid": "fr1.fr"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "fr1.fr_es1.es"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 96.682749}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "fr1.fr"}}, "endpoint_uuid": {"uuid": "es1.es"}}, + {"device_id": {"device_uuid": {"uuid": "es1.es"}}, "endpoint_uuid": {"uuid": "fr1.fr"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "es1.es_pt1.pt"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 5.643483}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "es1.es"}}, "endpoint_uuid": {"uuid": "pt1.pt"}}, + {"device_id": {"device_uuid": {"uuid": "pt1.pt"}}, "endpoint_uuid": {"uuid": "es1.es"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "es1.es_it1.it"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 15.353667}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "es1.es"}}, "endpoint_uuid": {"uuid": "it1.it"}}, + {"device_id": {"device_uuid": {"uuid": "it1.it"}}, "endpoint_uuid": {"uuid": "es1.es"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "es1.es_fr1.fr"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 20.517778}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "es1.es"}}, "endpoint_uuid": {"uuid": "fr1.fr"}}, + {"device_id": {"device_uuid": {"uuid": "fr1.fr"}}, "endpoint_uuid": {"uuid": "es1.es"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "de1.de_uk1.uk"}}, "attributes": {"total_capacity_gbps": 600, "used_capacity_gbps": 239.446965}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "de1.de"}}, "endpoint_uuid": {"uuid": "uk1.uk"}}, + {"device_id": {"device_uuid": {"uuid": "uk1.uk"}}, "endpoint_uuid": {"uuid": "de1.de"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "de1.de_gr1.gr"}}, "attributes": {"total_capacity_gbps": 2100, "used_capacity_gbps": 110.602237}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "de1.de"}}, "endpoint_uuid": {"uuid": "gr1.gr"}}, + {"device_id": {"device_uuid": {"uuid": "gr1.gr"}}, "endpoint_uuid": {"uuid": "de1.de"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "de1.de_be1.be"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 57.709307}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "de1.de"}}, "endpoint_uuid": {"uuid": "be1.be"}}, + {"device_id": {"device_uuid": {"uuid": "be1.be"}}, "endpoint_uuid": {"uuid": "de1.de"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "uk1.uk_pt1.pt"}}, "attributes": {"total_capacity_gbps": 800, "used_capacity_gbps": 652.70225}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "uk1.uk"}}, "endpoint_uuid": {"uuid": "pt1.pt"}}, + {"device_id": {"device_uuid": {"uuid": "pt1.pt"}}, "endpoint_uuid": {"uuid": "uk1.uk"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "be1.be_uk1.uk"}}, "attributes": {"total_capacity_gbps": 200, "used_capacity_gbps": 8.252107}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "be1.be"}}, "endpoint_uuid": {"uuid": "uk1.uk"}}, + {"device_id": {"device_uuid": {"uuid": "uk1.uk"}}, "endpoint_uuid": {"uuid": "be1.be"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "be1.be_it1.it"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 0.357069}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "be1.be"}}, "endpoint_uuid": {"uuid": "it1.it"}}, + {"device_id": {"device_uuid": {"uuid": "it1.it"}}, "endpoint_uuid": {"uuid": "be1.be"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "be1.be_de1.de"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 20.400142}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "be1.be"}}, "endpoint_uuid": {"uuid": "de1.de"}}, + {"device_id": {"device_uuid": {"uuid": "de1.de"}}, "endpoint_uuid": {"uuid": "be1.be"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "be1.be_fr1.fr"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 31.346514}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "be1.be"}}, "endpoint_uuid": {"uuid": "fr1.fr"}}, + {"device_id": {"device_uuid": {"uuid": "fr1.fr"}}, "endpoint_uuid": {"uuid": "be1.be"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "be1.be_gr1.gr"}}, "attributes": {"total_capacity_gbps": 100, "used_capacity_gbps": 0.026822}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "be1.be"}}, "endpoint_uuid": {"uuid": "gr1.gr"}}, + {"device_id": {"device_uuid": {"uuid": "gr1.gr"}}, "endpoint_uuid": {"uuid": "be1.be"}} + ]} + ] +} diff --git a/src/dlt/gateway/samples/topo4.json b/src/dlt/gateway/samples/topo4.json new file mode 100644 index 000000000..85bbad55e --- /dev/null +++ b/src/dlt/gateway/samples/topo4.json @@ -0,0 +1,150 @@ +{ + "contexts": [ + {"context_id": {"context_uuid": {"uuid": "admin"}}} + ], + "topologies": [ + {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}} + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "T1"}}, "device_type": "optical-transponder", "device_drivers": [11], + "device_operational_status": 1, + "device_endpoints": [ + {"endpoint_id": { + "device_id": {"device_uuid": {"uuid": "T1"}}, "endpoint_uuid": {"uuid": "1"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }} + ], + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.254.253.101"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "2022"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "admin", "force_running": false, "hostkey_verify": false, + "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, + "device_params": {"name": "default"}, "manager_params": {"timeout": 120}, + "endpoints": [{"uuid": "1", "type": "optical", "sample_types": [101, 102, 201, 202]}] + }}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "T2"}}, "device_type": "optical-transponder", "device_drivers": [11], + "device_operational_status": 1, + "device_endpoints": [ + {"endpoint_id": { + "device_id": {"device_uuid": {"uuid": "T2"}}, "endpoint_uuid": {"uuid": "6"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }} + ], + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.254.253.102"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "2022"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "admin", "force_running": false, "hostkey_verify": false, + "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, + "device_params": {"name": "default"}, "manager_params": {"timeout": 120}, + "endpoints": [{"uuid": "6", "type": "optical", "sample_types": [101, 102, 201, 202]}] + }}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "device_type": "optical-roadm", "device_drivers": [11], + "device_operational_status": 1, + "device_endpoints": [ + {"endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "2"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }}, + {"endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "3"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }}, + {"endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "12"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }}, + {"endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "13"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }} + ], + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.254.253.201"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "2022"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "admin", "force_running": false, "hostkey_verify": false, + "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, + "device_params": {"name": "default"}, "manager_params": {"timeout": 120}, + "endpoints": [ + {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "2"}, + {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "3"}, + {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "12"}, + {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "13"} + ]} + }}] + } + }, + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "device_type": "optical-roadm", "device_drivers": [11], + "device_operational_status": 1, + "device_endpoints": [ + {"endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "4"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }}, + {"endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "5"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }}, + {"endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "14"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }}, + {"endpoint_id": { + "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "15"}, + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} + }} + ], + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.254.253.202"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "2022"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "admin", "force_running": false, "hostkey_verify": false, + "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, + "device_params": {"name": "default"}, "manager_params": {"timeout": 120}, + "endpoints": [ + {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "4"}, + {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "5"}, + {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "14"}, + {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "15"} + ] + }}} + ]} + } + ], + "links": [ + {"link_id": {"link_uuid": {"uuid": "T1->R1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "T1"}}, "endpoint_uuid": {"uuid": "1"}}, + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "12"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R1->T1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "2"}}, + {"device_id": {"device_uuid": {"uuid": "T1"}}, "endpoint_uuid": {"uuid": "1"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R1->R2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "3"}}, + {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "14"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R2->R1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "4"}}, + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "13"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "T2->R2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "T2"}}, "endpoint_uuid": {"uuid": "6"}}, + {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "15"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "R2->T2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "5"}}, + {"device_id": {"device_uuid": {"uuid": "T2"}}, "endpoint_uuid": {"uuid": "6"}} + ]} + ] +} diff --git a/src/dlt/gateway/samples/updatedTopo.json b/src/dlt/gateway/samples/updatedTopo.json new file mode 100644 index 000000000..17f927c89 --- /dev/null +++ b/src/dlt/gateway/samples/updatedTopo.json @@ -0,0 +1,17 @@ +{ + "name": "Updated Network", + "nodes": [ + { + "id": "node1", + "type": "switch", + "status": "active", + "connections": ["node2"] + }, + { + "id": "node2", + "type": "router", + "status": "inactive", + "connections": ["node1"] + } + ] +} \ No newline at end of file diff --git a/src/dlt/gateway/settings.gradle.kts b/src/dlt/gateway/settings.gradle.kts deleted file mode 100644 index 77fa0f0b2..000000000 --- a/src/dlt/gateway/settings.gradle.kts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -rootProject.name = "gateway" - diff --git a/src/dlt/gateway/src/main/kotlin/Main.kt b/src/dlt/gateway/src/main/kotlin/Main.kt deleted file mode 100644 index c57c9e980..000000000 --- a/src/dlt/gateway/src/main/kotlin/Main.kt +++ /dev/null @@ -1,161 +0,0 @@ -// NEC Laboratories Europe GmbH -// -// PROPRIETARY INFORMATION -// -// The software and its source code contain valuable trade secrets and -// shall be maintained in confidence and treated as confidential -// information. The software may only be used for evaluation and/or -// testing purposes, unless otherwise explicitly stated in a written -// agreement with NEC Laboratories Europe GmbH. -// -// Any unauthorized publication, transfer to third parties or -// duplication of the object or source code - either totally or in -// part - is strictly prohibited. -// -// Copyright (c) 2022 NEC Laboratories Europe GmbH -// All Rights Reserved. -// -// Authors: Konstantin Munichev -// -// -// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE -// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND -// THE ACCOMPANYING DOCUMENTATION. -// -// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC -// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR -// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR -// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF -// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, -// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF -// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe -// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. - -import context.ContextOuterClass -import io.grpc.ManagedChannel -import io.grpc.ManagedChannelBuilder -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import dlt.DltGateway -import dlt.DltGatewayServiceGrpcKt -import java.io.Closeable -import java.util.* -import java.util.concurrent.TimeUnit - -class DltServiceClient(private val channel: ManagedChannel) : Closeable { - private val stub: DltGatewayServiceGrpcKt.DltGatewayServiceCoroutineStub = - DltGatewayServiceGrpcKt.DltGatewayServiceCoroutineStub(channel) - - suspend fun putData(data: DltGateway.DltRecord) { - println("Sending record ${data.recordId}...") - val response = stub.recordToDlt(data) - println("Response: ${response.recordId}") - } - - suspend fun getData(id: DltGateway.DltRecordId) { - println("Requesting record $id...") - val response = stub.getFromDlt(id) - println("Got data: $response") - } - - fun subscribe(filter: DltGateway.DltRecordSubscription) { - val subscription = stub.subscribeToDlt(filter) - GlobalScope.launch { - subscription.collect { - println("Got subscription event") - println(it) - } - } - } - - override fun close() { - channel.shutdown().awaitTermination(5, TimeUnit.SECONDS) - } -} - - -fun main() = runBlocking { - val port = 50051 - val channel = ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build() - - val client = DltServiceClient(channel) - - val domainUuid = UUID.randomUUID().toString() - val recordUuid = UUID.randomUUID().toString() - println("New domain uuid $domainUuid") - println("New record uuid $recordUuid") - - val id = DltGateway.DltRecordId.newBuilder() - .setDomainUuid( - ContextOuterClass.Uuid.newBuilder() - .setUuid(domainUuid) - ) - .setRecordUuid( - ContextOuterClass.Uuid.newBuilder() - .setUuid(recordUuid) - ) - .setType(DltGateway.DltRecordTypeEnum.DLTRECORDTYPE_SERVICE) - .build() - - val subscription = DltGateway.DltRecordSubscription.newBuilder() - .addType(DltGateway.DltRecordTypeEnum.DLTRECORDTYPE_CONTEXT) - .addType(DltGateway.DltRecordTypeEnum.DLTRECORDTYPE_LINK) - .addType(DltGateway.DltRecordTypeEnum.DLTRECORDTYPE_SERVICE) - .addOperation(DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_ADD) - .addOperation(DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_UPDATE) - .addOperation(DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_DELETE) - .build() - - client.subscribe(subscription) - - Thread.sleep(5000) - - val data = DltGateway.DltRecord.newBuilder() - .setRecordId(id) - .setOperation(DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_ADD) - .setDataJson("\"{\"device_config\": {\"config_rules\": []}, \"device_drivers\": []," + - "\"device_endpoints\": [], \"device_id\": {\"device_uuid\": {\"uuid\": \"dev-12345\"}}," + - "\"device_operational_status\": \"DEVICEOPERATIONALSTATUS_ENABLED\"," + - "\"device_type\": \"packet-router\"}\", \"operation\": \"DLTRECORDOPERATION_ADD\"," + - "\"record_id\": {\"domain_uuid\": {\"uuid\": \"tfs-a\"}, \"record_uuid\": {\"uuid\": \"dev-12345\"}," + - "\"type\": \"DLTRECORDTYPE_DEVICE\"}") - .build() - - println("sending new record") - client.putData(data) - client.getData(id) - - Thread.sleep(5000) - - val updateData = DltGateway.DltRecord.newBuilder() - .setRecordId(id) - .setOperation(DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_UPDATE) - .setDataJson("{\"name\": \"test\"}") - .build() - - println("updating record") - client.putData(updateData) - client.getData(id) - - Thread.sleep(5000) - - val removeData = DltGateway.DltRecord.newBuilder() - .setRecordId(id) - .setOperation(DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_DELETE) - .setDataJson("{\"name\": \"test\"}") - .build() - - println("removing record") - client.putData(removeData) - try { - client.getData(id) - } catch (e: Exception) { - println(e.toString()) - } - Thread.sleep(5000) -} diff --git a/src/dlt/gateway/src/main/kotlin/fabric/ConnectGateway.kt b/src/dlt/gateway/src/main/kotlin/fabric/ConnectGateway.kt deleted file mode 100644 index 00ec40d57..000000000 --- a/src/dlt/gateway/src/main/kotlin/fabric/ConnectGateway.kt +++ /dev/null @@ -1,54 +0,0 @@ -// NEC Laboratories Europe GmbH -// -// PROPRIETARY INFORMATION -// -// The software and its source code contain valuable trade secrets and -// shall be maintained in confidence and treated as confidential -// information. The software may only be used for evaluation and/or -// testing purposes, unless otherwise explicitly stated in a written -// agreement with NEC Laboratories Europe GmbH. -// -// Any unauthorized publication, transfer to third parties or -// duplication of the object or source code - either totally or in -// part - is strictly prohibited. -// -// Copyright (c) 2022 NEC Laboratories Europe GmbH -// All Rights Reserved. -// -// Authors: Konstantin Munichev -// -// -// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE -// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND -// THE ACCOMPANYING DOCUMENTATION. -// -// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC -// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR -// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR -// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF -// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, -// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF -// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe -// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. - -package fabric - -import org.hyperledger.fabric.gateway.Contract -import org.hyperledger.fabric.gateway.Gateway -import org.hyperledger.fabric.gateway.Wallet -import java.nio.file.Paths - -// helper function for getting connected to the gateway -fun getContract(config: proto.Config.DltConfig, wallet: Wallet): Contract { - // load a CCP - val networkConfigPath = Paths.get(config.connectionFile) - val builder = Gateway.createBuilder() - builder.identity(wallet, config.user).networkConfig(networkConfigPath).discovery(true) - val gateway = builder.connect() - val network = gateway.getNetwork(config.channel) - return network.getContract(config.contract) -} \ No newline at end of file diff --git a/src/dlt/gateway/src/main/kotlin/fabric/EnrollAdmin.kt b/src/dlt/gateway/src/main/kotlin/fabric/EnrollAdmin.kt deleted file mode 100644 index b44202719..000000000 --- a/src/dlt/gateway/src/main/kotlin/fabric/EnrollAdmin.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright IBM Corp. All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package fabric - -import org.hyperledger.fabric.gateway.Identities -import org.hyperledger.fabric.gateway.Wallet -import org.hyperledger.fabric_ca.sdk.EnrollmentRequest -import org.hyperledger.fabric_ca.sdk.HFCAClient - -fun enrollAdmin(config: proto.Config.DltConfig, caClient: HFCAClient, wallet: Wallet) { - // Check to see if we've already enrolled the admin user. - if (wallet.get(config.caAdmin) != null) { - println("An identity for the admin user ${config.caAdmin} already exists in the wallet") - return - } - - // Enroll the admin user, and import the new identity into the wallet. - val enrollmentRequestTLS = EnrollmentRequest() - enrollmentRequestTLS.addHost(config.caUrl) - enrollmentRequestTLS.profile = "tls" - val enrollment = caClient.enroll(config.caAdmin, config.caAdminSecret, enrollmentRequestTLS) - val user = Identities.newX509Identity(config.msp, enrollment) - wallet.put(config.caAdmin, user) - println("Successfully enrolled user ${config.caAdmin} and imported it into the wallet") -} diff --git a/src/dlt/gateway/src/main/kotlin/fabric/FabricConnector.kt b/src/dlt/gateway/src/main/kotlin/fabric/FabricConnector.kt deleted file mode 100644 index af6592be9..000000000 --- a/src/dlt/gateway/src/main/kotlin/fabric/FabricConnector.kt +++ /dev/null @@ -1,178 +0,0 @@ -// NEC Laboratories Europe GmbH -// -// PROPRIETARY INFORMATION -// -// The software and its source code contain valuable trade secrets and -// shall be maintained in confidence and treated as confidential -// information. The software may only be used for evaluation and/or -// testing purposes, unless otherwise explicitly stated in a written -// agreement with NEC Laboratories Europe GmbH. -// -// Any unauthorized publication, transfer to third parties or -// duplication of the object or source code - either totally or in -// part - is strictly prohibited. -// -// Copyright (c) 2022 NEC Laboratories Europe GmbH -// All Rights Reserved. -// -// Authors: Konstantin Munichev -// -// -// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE -// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND -// THE ACCOMPANYING DOCUMENTATION. -// -// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC -// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR -// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR -// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF -// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, -// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF -// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe -// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. - -package fabric - -import context.ContextOuterClass -import dlt.DltGateway.DltRecord -import dlt.DltGateway.DltRecordEvent -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.runBlocking -import org.hyperledger.fabric.gateway.Contract -import org.hyperledger.fabric.gateway.ContractEvent -import org.hyperledger.fabric.gateway.Wallet -import org.hyperledger.fabric.gateway.Wallets -import org.hyperledger.fabric.sdk.security.CryptoSuiteFactory -import org.hyperledger.fabric_ca.sdk.HFCAClient -import proto.Config -import java.nio.file.Paths -import java.util.* -import java.util.function.Consumer - -class FabricConnector(val config: Config.DltConfig) { - private val caClient: HFCAClient - private val wallet: Wallet - private val contract: Contract - - private val channels: MutableList> = mutableListOf() - - private val encoder: Base64.Encoder = Base64.getEncoder() - private val decoder: Base64.Decoder = Base64.getDecoder() - - init { - // Create a CA client for interacting with the CA. - val props = Properties() - props["pemFile"] = config.caCertFile - props["allowAllHostNames"] = "true" - caClient = HFCAClient.createNewInstance(config.caUrl, props) - val cryptoSuite = CryptoSuiteFactory.getDefault().cryptoSuite - caClient.cryptoSuite = cryptoSuite - - // Create a wallet for managing identities - wallet = Wallets.newFileSystemWallet(Paths.get(config.wallet)) - contract = connect() - - fabricSubscribe() - } - - private fun fabricSubscribe() { - val consumer = Consumer { event: ContractEvent? -> - run { - println("new event detected") - val record = DltRecord.parseFrom(decoder.decode(event?.payload?.get())) - println(record.recordId.recordUuid) - val eventType: ContextOuterClass.EventTypeEnum = when (event?.name) { - "Add" -> ContextOuterClass.EventTypeEnum.EVENTTYPE_CREATE - "Update" -> ContextOuterClass.EventTypeEnum.EVENTTYPE_UPDATE - "Remove" -> ContextOuterClass.EventTypeEnum.EVENTTYPE_REMOVE - else -> ContextOuterClass.EventTypeEnum.EVENTTYPE_UNDEFINED - } - val pbEvent = DltRecordEvent.newBuilder() - .setEvent( - ContextOuterClass.Event.newBuilder() - .setTimestamp( - ContextOuterClass.Timestamp.newBuilder() - .setTimestamp(System.currentTimeMillis().toDouble()) - ) - .setEventType(eventType) - ) - .setRecordId(record.recordId) - .build() - - runBlocking { - channels.forEach { - it.trySend(pbEvent) - } - } - } - } - contract.addContractListener(consumer) - } - - fun connect(): Contract { - enrollAdmin(config, caClient, wallet) - registerUser(config, caClient, wallet) - return getContract(config, wallet) - } - - fun putData(record: DltRecord): String { - println(record.toString()) - - try { - contract.submitTransaction( - "AddRecord", - record.recordId.recordUuid.uuid, - encoder.encodeToString(record.toByteArray()) - ) - } catch (e: Exception) { - println(e.toString()) - return e.toString() - } - return "" - } - - fun getData(uuid: String): DltRecord { - return try { - val result = contract.evaluateTransaction("GetRecord", uuid) - DltRecord.parseFrom(decoder.decode(result)) - } catch (e: Exception) { - println(e.toString()) - DltRecord.getDefaultInstance() - } - } - - fun updateData(record: DltRecord): String { - try { - contract.submitTransaction( - "UpdateRecord", - record.recordId.recordUuid.uuid, - encoder.encodeToString(record.toByteArray()) - ) - } catch (e: Exception) { - return e.toString() - } - return "" - } - - fun deleteData(record: DltRecord): String { - try { - contract.submitTransaction( - "DeleteRecord", - record.recordId.recordUuid.uuid, - ) - } catch (e: Exception) { - return e.toString() - } - return "" - } - - fun subscribeForEvents(): Channel { - val produceCh = Channel() - channels.add(produceCh) - return produceCh - } -} \ No newline at end of file diff --git a/src/dlt/gateway/src/main/kotlin/fabric/RegisterUser.kt b/src/dlt/gateway/src/main/kotlin/fabric/RegisterUser.kt deleted file mode 100644 index fb5cc2969..000000000 --- a/src/dlt/gateway/src/main/kotlin/fabric/RegisterUser.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* -SPDX-License-Identifier: Apache-2.0 -*/ -package fabric - -import org.hyperledger.fabric.gateway.Identities -import org.hyperledger.fabric.gateway.Wallet -import org.hyperledger.fabric.gateway.X509Identity -import org.hyperledger.fabric.sdk.Enrollment -import org.hyperledger.fabric.sdk.User -import org.hyperledger.fabric_ca.sdk.HFCAClient -import org.hyperledger.fabric_ca.sdk.RegistrationRequest -import java.security.PrivateKey - -fun registerUser(config: proto.Config.DltConfig, caClient: HFCAClient, wallet: Wallet) { - // Check to see if we've already enrolled the user. - if (wallet[config.user] != null) { - println("An identity for the user ${config.user} already exists in the wallet") - return - } - val adminIdentity = wallet[config.caAdmin] as X509Identity - val admin = object : User { - override fun getName(): String { - return config.caAdmin - } - - override fun getRoles(): Set? { - return null - } - - override fun getAccount(): String? { - return null - } - - override fun getAffiliation(): String { - return config.affiliation - } - - override fun getEnrollment(): Enrollment { - return object : Enrollment { - override fun getKey(): PrivateKey { - return adminIdentity.privateKey - } - - override fun getCert(): String { - return Identities.toPemString(adminIdentity.certificate) - } - } - } - - override fun getMspId(): String { - return config.msp - } - } - - // Register the user, enroll the user, and import the new identity into the wallet. - val registrationRequest = RegistrationRequest(config.user) - registrationRequest.affiliation = config.affiliation - registrationRequest.enrollmentID = config.user - val enrollmentSecret = caClient.register(registrationRequest, admin) - val enrollment = caClient.enroll(config.user, enrollmentSecret) - val user = Identities.newX509Identity(config.msp, enrollment) - wallet.put(config.user, user) - println("Successfully enrolled user ${config.user} and imported it into the wallet") -} diff --git a/src/dlt/gateway/src/main/kotlin/grpc/FabricServer.kt b/src/dlt/gateway/src/main/kotlin/grpc/FabricServer.kt deleted file mode 100644 index 9b4e1f4dc..000000000 --- a/src/dlt/gateway/src/main/kotlin/grpc/FabricServer.kt +++ /dev/null @@ -1,94 +0,0 @@ -// NEC Laboratories Europe GmbH -// -// PROPRIETARY INFORMATION -// -// The software and its source code contain valuable trade secrets and -// shall be maintained in confidence and treated as confidential -// information. The software may only be used for evaluation and/or -// testing purposes, unless otherwise explicitly stated in a written -// agreement with NEC Laboratories Europe GmbH. -// -// Any unauthorized publication, transfer to third parties or -// duplication of the object or source code - either totally or in -// part - is strictly prohibited. -// -// Copyright (c) 2022 NEC Laboratories Europe GmbH -// All Rights Reserved. -// -// Authors: Konstantin Munichev -// -// -// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE -// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND -// THE ACCOMPANYING DOCUMENTATION. -// -// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC -// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR -// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR -// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF -// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, -// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF -// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe -// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. - -package grpc - -import fabric.FabricConnector -import io.grpc.Server -import io.grpc.ServerBuilder -import proto.Config -import kotlin.random.Random -import kotlin.random.nextUInt - -class FabricServer(val port: Int) { - private val server: Server - - init { - val id = Random.nextUInt() - val cfg = Config.DltConfig.newBuilder().setWallet("wallet$id").setConnectionFile("config/connection-org1.json") - .setUser("appUser$id") - .setChannel("dlt") - .setContract("basic").setCaCertFile("config/ca.org1.example.com-cert.pem").setCaUrl("https://teraflow.nlehd.de:7054") - .setCaAdmin("admin").setCaAdminSecret("adminpw").setMsp("Org1MSP").setAffiliation("org1.department1") - .build() - val connector = FabricConnector(cfg) - - val dltService = DLTService(connector) - server = ServerBuilder - .forPort(port) - .addService(dltService) - .build() - - } - - fun start() { - server.start() - println("Server started, listening on $port") - Runtime.getRuntime().addShutdownHook( - Thread { - println("Shutting down...") - this@FabricServer.stop() - println("Server shut down") - } - ) - } - - private fun stop() { - server.shutdown() - } - - fun blockUntilShutdown() { - server.awaitTermination() - } -} - -fun main() { - val port = 50051 - val server = FabricServer(port) - server.start() - server.blockUntilShutdown() -} diff --git a/src/dlt/gateway/src/main/kotlin/grpc/GrpcHandler.kt b/src/dlt/gateway/src/main/kotlin/grpc/GrpcHandler.kt deleted file mode 100644 index d39c24a1a..000000000 --- a/src/dlt/gateway/src/main/kotlin/grpc/GrpcHandler.kt +++ /dev/null @@ -1,95 +0,0 @@ -// NEC Laboratories Europe GmbH -// -// PROPRIETARY INFORMATION -// -// The software and its source code contain valuable trade secrets and -// shall be maintained in confidence and treated as confidential -// information. The software may only be used for evaluation and/or -// testing purposes, unless otherwise explicitly stated in a written -// agreement with NEC Laboratories Europe GmbH. -// -// Any unauthorized publication, transfer to third parties or -// duplication of the object or source code - either totally or in -// part - is strictly prohibited. -// -// Copyright (c) 2022 NEC Laboratories Europe GmbH -// All Rights Reserved. -// -// Authors: Konstantin Munichev -// -// -// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE -// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND -// THE ACCOMPANYING DOCUMENTATION. -// -// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC -// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR -// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR -// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF -// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, -// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF -// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe -// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. - -package grpc - -import fabric.FabricConnector -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.consumeAsFlow -import context.ContextOuterClass -import dlt.DltGateway -import dlt.DltGatewayServiceGrpcKt - -class DLTService(private val connector: FabricConnector) : - DltGatewayServiceGrpcKt.DltGatewayServiceCoroutineImplBase() { - override suspend fun recordToDlt(request: DltGateway.DltRecord): DltGateway.DltRecordStatus { - println("Incoming request ${request.recordId.recordUuid}") - val error = when (request.operation) { - DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_ADD -> { - println("Adding new record") - connector.putData(request) - } - DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_UPDATE -> { - println("Updating record") - connector.updateData(request) - } - DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_DELETE -> { - println("Deleting record") - connector.deleteData(request) - } - else -> "Undefined or unknown operation" - } - - val dltStatusEnum: DltGateway.DltRecordStatusEnum = if (error == "") { - DltGateway.DltRecordStatusEnum.DLTRECORDSTATUS_SUCCEEDED - } else { - DltGateway.DltRecordStatusEnum.DLTRECORDSTATUS_FAILED - } - return DltGateway.DltRecordStatus.newBuilder() - .setRecordId(request.recordId) - .setStatus(dltStatusEnum) - .setErrorMessage(error) - .build() - } - - override suspend fun getFromDlt(request: DltGateway.DltRecordId): DltGateway.DltRecord { - return connector.getData(request.recordUuid.uuid) - } - - override fun subscribeToDlt(request: DltGateway.DltRecordSubscription): Flow { - println("Subscription request: $request") - return connector.subscribeForEvents().consumeAsFlow() - } - - override suspend fun getDltStatus(request: ContextOuterClass.TeraFlowController): DltGateway.DltPeerStatus { - return super.getDltStatus(request) - } - - override suspend fun getDltPeers(request: ContextOuterClass.Empty): DltGateway.DltPeerStatusList { - return super.getDltPeers(request) - } -} \ No newline at end of file diff --git a/src/dlt/gateway/src/main/kotlin/proto/Config.proto b/src/dlt/gateway/src/main/kotlin/proto/Config.proto deleted file mode 100644 index b6d4c5614..000000000 --- a/src/dlt/gateway/src/main/kotlin/proto/Config.proto +++ /dev/null @@ -1,54 +0,0 @@ -// NEC Laboratories Europe GmbH -// -// PROPRIETARY INFORMATION -// -// The software and its source code contain valuable trade secrets and -// shall be maintained in confidence and treated as confidential -// information. The software may only be used for evaluation and/or -// testing purposes, unless otherwise explicitly stated in a written -// agreement with NEC Laboratories Europe GmbH. -// -// Any unauthorized publication, transfer to third parties or -// duplication of the object or source code - either totally or in -// part - is strictly prohibited. -// -// Copyright (c) 2022 NEC Laboratories Europe GmbH -// All Rights Reserved. -// -// Authors: Konstantin Munichev -// -// -// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES -// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE -// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND -// THE ACCOMPANYING DOCUMENTATION. -// -// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC -// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR -// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR -// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF -// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, -// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF -// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe -// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. - -syntax = "proto3"; - -package proto; - -message DltConfig { - string wallet = 1; - string connectionFile = 2; - string user = 3; - string channel = 4; - string contract = 5; - string caCertFile = 6; - string caUrl = 7; - string caAdmin = 8; - string caAdminSecret = 9; - string msp = 10; - string affiliation = 11; -} -- GitLab From 48cb33f4ec128ff59bf2ac1567b95f413d073fa3 Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 20 Jun 2024 12:46:02 +0200 Subject: [PATCH 02/94] Updated DLT Gateway code. Related to Issue #165 --- src/dlt/gateway/{dltApp => }/README.md | 4 ++-- src/dlt/gateway/resources/System.png | Bin 0 -> 291845 bytes 2 files changed, 2 insertions(+), 2 deletions(-) rename src/dlt/gateway/{dltApp => }/README.md (92%) create mode 100644 src/dlt/gateway/resources/System.png diff --git a/src/dlt/gateway/dltApp/README.md b/src/dlt/gateway/README.md similarity index 92% rename from src/dlt/gateway/dltApp/README.md rename to src/dlt/gateway/README.md index feee576c1..d9a29e3b6 100644 --- a/src/dlt/gateway/dltApp/README.md +++ b/src/dlt/gateway/README.md @@ -1,8 +1,8 @@ # ADRENALINE DLT App
- DLT_APP for chaincode tests
DLT_APP for chaincode tests.
diff --git a/src/dlt/gateway/resources/System.png b/src/dlt/gateway/resources/System.png new file mode 100644 index 0000000000000000000000000000000000000000..f8c6b79f08e44842e7a486ede2477b987cbeb9d7 GIT binary patch literal 291845 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
0`mI47*@tD5cxnKl3TA+U)& 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⁣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 z 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 kimS{O&&+3Q-Hc`27pg45C8xG literal 0 HcmV?d00001 -- GitLab From 78dae728a491a74c57654d0689f74374ff8324f9 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 21 Jun 2024 10:29:05 +0200 Subject: [PATCH 03/94] Updated Readme, Ref. Issue #165 --- src/dlt/gateway/README.md | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/dlt/gateway/README.md b/src/dlt/gateway/README.md index d9a29e3b6..7206289ec 100644 --- a/src/dlt/gateway/README.md +++ b/src/dlt/gateway/README.md @@ -10,37 +10,48 @@ ## Description -The DLT app consists of a **fabricConnect.ts** TypeScript file which contains the logic for Identification management (Certificates required for the MSP), connection management to the blockchain, and finally it exposes a contract object with all the required information for interacting with the chaincode. The compiled **fabricConnect.ts** logic can be imported into a **dltGateway.js** or other testing code inside the [/tests](./tests/) folder. +The DLT app consists of a **fabricConnect.ts** TypeScript file, which contains the logic for identification management (certificates required for the MSP), connection management to the blockchain, and finally, it exposes a contract object with all the required information for interacting with the chaincode. The **fabricConnect.ts** is coded following the Fabric Gateway API recommendations from Hyperledger Fabric 2.4+. The compiled **fabricConnect.ts** logic is imported into a **dltGateway.js** file, which contains the gRPC logic for interaction with the TFS controller. Testing code for various performance tests is included inside the [/dltApp/tests](./dltApp/tests/) folder. + +The chaincode is written in Go, providing a reference for the operations that are recorded in the blockchain. This chaincode must already be deployed in a working Hyperledger Fabric blockchain. ## Requisites -NodeJS +* NodeJS +* Docker +* K8s -## Running the App +## Building the App -Install the dependencies and compile the sourcecode. +Install the dependencies and compile the source code. ```bash npm install - ``` -Run the Gateway application +## Packing the App -```bash -node .\src\dltGateway.js +The [automation](./automation/) folder contains the Dockerfiles and Kubernetes configuration files alongside deployment scripts. -``` +### Build a Docker Image -In another terminal run the test client application. +Using the Dockerfile, create a Docker image of the Gateway. ```bash -node .\src\testGateway.js +docker build -t your-username/dltgateway:v1.0.0 . +``` +If necessary, upload the Docker image to a Docker repository for convenience. (You can work with the local image if deploying the Kubernetes service on the same machine.) + +```bash +docker push your-username/dltgateway:v1.0.0 ``` -The purpose of the dltGateway is to expose the chaincode operations to gRPC connections for integration with the ADRENALINE testbed modules. +### Run the Deployment Script +Make the necessary changes to the environment variables inside the **configmap.yaml** according to the specific Fabric deployment. Also, update the path in the **persistent-volume.yaml** with the correct information. -## Performance Test +```bash +./deploy_dlt_gateway.sh +``` +Once the Kubernetes service is deployed, TFS can perform gRPC requests to as usual. \ No newline at end of file -- GitLab From a741f76ff964a9ad12785440e9fb2019fd991e1b Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 13:18:15 +0200 Subject: [PATCH 04/94] Updated the Manifest files and deployment scripts for DLT --- deploy/all.sh | 2 +- manifests/dltservice.yaml | 205 ++++++++++++++---- my_deploy.sh | 2 +- .../{automation/DockerFiles => }/Dockerfile | 0 src/dlt/gateway/README.md | 2 +- 5 files changed, 162 insertions(+), 49 deletions(-) rename src/dlt/gateway/{automation/DockerFiles => }/Dockerfile (100%) diff --git a/deploy/all.sh b/deploy/all.sh index c169bc92c..e77ff22ae 100755 --- a/deploy/all.sh +++ b/deploy/all.sh @@ -27,7 +27,7 @@ export TFS_REGISTRY_IMAGES=${TFS_REGISTRY_IMAGES:-"http://localhost:32000/tfs/"} # If not already set, set the list of components, separated by spaces, you want to build images for, and deploy. # By default, only basic components are deployed -export TFS_COMPONENTS=${TFS_COMPONENTS:-"context device ztp monitoring pathcomp service slice nbi webui load_generator"} +export TFS_COMPONENTS=${TFS_COMPONENTS:-"context device ztp monitoring pathcomp service slice nbi webui load_generator dlt"} # If not already set, set the tag you want to use for your images. export TFS_IMAGE_TAG=${TFS_IMAGE_TAG:-"dev"} diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index 34f0d53c3..5362fe6ab 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -12,10 +12,29 @@ # See the License for the specific language governing permissions and # limitations under the License. +apiVersion: v1 +kind: ConfigMap +metadata: + name: dlt-config + namespace: dlt +data: + CHANNEL_NAME: "channel1" + CHAINCODE_NAME: "adrenalineDLT" + MSP_ID: "Org1MSP" + PEER_ENDPOINT: "PEER_IP:PORT" + PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" + CRYPTO_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com" + KEY_DIRECTORY_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/keystore" + CERT_DIRECTORY_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/signcerts" + TLS_CERT_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/peers/peer0.org1.adrenaline.com/tls/ca.crt" + +--- + apiVersion: apps/v1 kind: Deployment metadata: name: dltservice + namespace: dlt spec: selector: matchLabels: @@ -27,62 +46,156 @@ spec: spec: terminationGracePeriodSeconds: 5 containers: - - name: connector - image: labs.etsi.org:5050/tfs/controller/dlt-connector:latest - imagePullPolicy: Always - ports: - - containerPort: 8080 - - containerPort: 9192 - env: - - name: LOG_LEVEL - value: "INFO" + - name: connector + image: labs.etsi.org:5050/tfs/controller/dlt-connector:latest + imagePullPolicy: Always + ports: + - containerPort: 8080 + - containerPort: 9192 + env: + - name: LOG_LEVEL + value: "INFO" ## for debug purposes #- name: DLT_GATEWAY_HOST # value: "mock-blockchain.tfs-bchain.svc.cluster.local" #- name: DLT_GATEWAY_PORT # value: "50051" - readinessProbe: - exec: - command: ["/bin/grpc_health_probe", "-addr=:8080"] - livenessProbe: - exec: - command: ["/bin/grpc_health_probe", "-addr=:8080"] - resources: - requests: - cpu: 50m - memory: 64Mi - limits: - cpu: 500m - memory: 512Mi - - name: gateway - image: labs.etsi.org:5050/tfs/controller/dlt-gateway:latest - imagePullPolicy: Always - ports: - - containerPort: 50051 - #readinessProbe: - # httpGet: - # path: /health - # port: 8081 - # initialDelaySeconds: 5 - # timeoutSeconds: 5 - #livenessProbe: - # httpGet: - # path: /health - # port: 8081 - # initialDelaySeconds: 5 - # timeoutSeconds: 5 - resources: - requests: - cpu: 200m - memory: 512Mi - limits: - cpu: 700m - memory: 1024Mi + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:8080"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:8080"] + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 500m + memory: 512Mi + - name: gateway + image: labs.etsi.org:5050/tfs/controller/dlt-gateway:latest + imagePullPolicy: Always + ports: + - containerPort: 50051 + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 700m + memory: 1024Mi + volumeMounts: + - mountPath: /test-network + name: dlt-volume + readOnly: true + env: + - name: CHANNEL_NAME + valueFrom: + configMapKeyRef: + name: dlt-config + key: CHANNEL_NAME + - name: CHAINCODE_NAME + valueFrom: + configMapKeyRef: + name: dlt-config + key: CHAINCODE_NAME + - name: MSP_ID + valueFrom: + configMapKeyRef: + name: dlt-config + key: MSP_ID + - name: PEER_ENDPOINT + valueFrom: + configMapKeyRef: + name: dlt-config + key: PEER_ENDPOINT + - name: PEER_HOST_ALIAS + valueFrom: + configMapKeyRef: + name: dlt-config + key: PEER_HOST_ALIAS + - name: CRYPTO_PATH + valueFrom: + configMapKeyRef: + name: dlt-config + key: CRYPTO_PATH + - name: KEY_DIRECTORY_PATH + valueFrom: + configMapKeyRef: + name: dlt-config + key: KEY_DIRECTORY_PATH + - name: CERT_DIRECTORY_PATH + valueFrom: + configMapKeyRef: + name: dlt-config + key: CERT_DIRECTORY_PATH + - name: TLS_CERT_PATH + valueFrom: + configMapKeyRef: + name: dlt-config + key: TLS_CERT_PATH + volumes: + - name: dlt-volume + persistentVolumeClaim: + claimName: dlt-pvc + --- + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: dlt-pvc + namespace: dlt +spec: + accessModes: + - ReadOnlyMany + resources: + requests: + storage: 1Gi + +--- + +apiVersion: v1 +kind: PersistentVolume +metadata: + name: dlt-pv +spec: + capacity: + storage: 1Gi + accessModes: + - ReadOnlyMany + persistentVolumeReclaimPolicy: Retain + hostPath: + path: "/home/cttc/test-network" + claimRef: + namespace: dlt + name: dlt-pvc + +--- + +apiVersion: v1 +kind: Service +metadata: + name: gatewayservice + namespace: dlt +spec: + selector: + app: dltservice + ports: + - protocol: TCP + port: 50051 + targetPort: 50051 + nodePort: 32001 + type: NodePort + +--- + apiVersion: v1 kind: Service metadata: name: dltservice + namespace: dlt labels: app: dltservice spec: diff --git a/my_deploy.sh b/my_deploy.sh index 8417f6eae..7cd94d136 100755 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -20,7 +20,7 @@ export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/" # Set the list of components, separated by spaces, you want to build images for, and deploy. -export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_generator" +export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_generator dlt" # Uncomment to activate Monitoring #export TFS_COMPONENTS="${TFS_COMPONENTS} monitoring" diff --git a/src/dlt/gateway/automation/DockerFiles/Dockerfile b/src/dlt/gateway/Dockerfile similarity index 100% rename from src/dlt/gateway/automation/DockerFiles/Dockerfile rename to src/dlt/gateway/Dockerfile diff --git a/src/dlt/gateway/README.md b/src/dlt/gateway/README.md index 7206289ec..401e17864 100644 --- a/src/dlt/gateway/README.md +++ b/src/dlt/gateway/README.md @@ -54,4 +54,4 @@ Make the necessary changes to the environment variables inside the **configmap.y ./deploy_dlt_gateway.sh ``` -Once the Kubernetes service is deployed, TFS can perform gRPC requests to as usual. \ No newline at end of file +Once the Kubernetes service is deployed, TFS can perform gRPC requests to as usual. \ No newline at end of file -- GitLab From 2c606fb2becb38a3a65a346eab56368bc70bde89 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 13:20:27 +0200 Subject: [PATCH 05/94] Updated deployment Script --- my_deploy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/my_deploy.sh b/my_deploy.sh index 7cd94d136..d18c5db7f 100755 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -106,7 +106,7 @@ export CRDB_DATABASE="tfs" export CRDB_DEPLOY_MODE="single" # Disable flag for dropping database, if it exists. -export CRDB_DROP_DATABASE_IF_EXISTS="" +export CRDB_DROP_DATABASE_IF_EXISTS="YES" # Disable flag for re-deploying CockroachDB from scratch. export CRDB_REDEPLOY="" @@ -154,7 +154,7 @@ export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis" export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups" # Disable flag for dropping tables if they exist. -export QDB_DROP_TABLES_IF_EXIST="" +export QDB_DROP_TABLES_IF_EXIST="YES" # Disable flag for re-deploying QuestDB from scratch. export QDB_REDEPLOY="" -- GitLab From dde6c4ed98241e44585c8d625977f7baf0060116 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 13:39:16 +0200 Subject: [PATCH 06/94] Updated deployment scripts --- deploy/tfs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/tfs.sh b/deploy/tfs.sh index 3fdbe77fb..57d647cde 100755 --- a/deploy/tfs.sh +++ b/deploy/tfs.sh @@ -27,7 +27,7 @@ export TFS_REGISTRY_IMAGES=${TFS_REGISTRY_IMAGES:-"http://localhost:32000/tfs/"} # If not already set, set the list of components, separated by spaces, you want to build images for, and deploy. # By default, only basic components are deployed -export TFS_COMPONENTS=${TFS_COMPONENTS:-"context device ztp monitoring pathcomp service slice nbi webui load_generator"} +export TFS_COMPONENTS=${TFS_COMPONENTS:-"context device ztp monitoring pathcomp service slice nbi webui load_generator dlt"} # If not already set, set the tag you want to use for your images. export TFS_IMAGE_TAG=${TFS_IMAGE_TAG:-"dev"} -- GitLab From fb62c881df96a7cf53a7ac352fa0a25b03d8b066 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 15:09:33 +0200 Subject: [PATCH 07/94] Updated tfs.sh deployment exists to control building of docker images --- deploy/tfs.sh | 113 +++++++++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 52 deletions(-) diff --git a/deploy/tfs.sh b/deploy/tfs.sh index 57d647cde..e81b946c8 100755 --- a/deploy/tfs.sh +++ b/deploy/tfs.sh @@ -45,6 +45,10 @@ export TFS_GRAFANA_PASSWORD=${TFS_GRAFANA_PASSWORD:-"admin123+"} # If TFS_SKIP_BUILD is "YES", the containers are not rebuilt-retagged-repushed and existing ones are used. export TFS_SKIP_BUILD=${TFS_SKIP_BUILD:-""} +# If not already set, disable build-if-exists flag to skip building Docker images if they already exist. +# If TFS_BUILD_IF_EXISTS is "NO", the containers are not rebuilt if they already exist. +export TFS_BUILD_IF_EXISTS=${TFS_BUILD_IF_EXISTS:-"NO"} + # ----- CockroachDB ------------------------------------------------------------ @@ -208,72 +212,77 @@ for COMPONENT in $TFS_COMPONENTS; do echo "Processing '$COMPONENT' component..." if [ "$TFS_SKIP_BUILD" != "YES" ]; then - echo " Building Docker image..." - BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}.log" - - if [ "$COMPONENT" == "ztp" ] || [ "$COMPONENT" == "policy" ]; then - $DOCKER_BUILD -t "$COMPONENT:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/Dockerfile ./src/"$COMPONENT"/ > "$BUILD_LOG" - elif [ "$COMPONENT" == "pathcomp" ]; then - BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-frontend.log" - $DOCKER_BUILD -t "$COMPONENT-frontend:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/frontend/Dockerfile . > "$BUILD_LOG" - - BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-backend.log" - $DOCKER_BUILD -t "$COMPONENT-backend:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/backend/Dockerfile . > "$BUILD_LOG" - # next command is redundant, but helpful to keep cache updated between rebuilds - IMAGE_NAME="$COMPONENT-backend:$TFS_IMAGE_TAG-builder" - $DOCKER_BUILD -t "$IMAGE_NAME" --target builder -f ./src/"$COMPONENT"/backend/Dockerfile . >> "$BUILD_LOG" - elif [ "$COMPONENT" == "dlt" ]; then - BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-connector.log" - $DOCKER_BUILD -t "$COMPONENT-connector:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/connector/Dockerfile . > "$BUILD_LOG" - - BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-gateway.log" - $DOCKER_BUILD -t "$COMPONENT-gateway:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/gateway/Dockerfile . > "$BUILD_LOG" - else - $DOCKER_BUILD -t "$COMPONENT:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/Dockerfile . > "$BUILD_LOG" - fi + IMAGE_EXISTS=$(docker images -q "$COMPONENT:$TFS_IMAGE_TAG") + if [ -z "$IMAGE_EXISTS" ] || [ "$TFS_BUILD_IF_EXISTS" == "YES" ]; then + echo " Building Docker image..." + BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}.log" - echo " Pushing Docker image to '$TFS_REGISTRY_IMAGES'..." + if [ "$COMPONENT" == "ztp" ] || [ "$COMPONENT" == "policy" ]; then + $DOCKER_BUILD -t "$COMPONENT:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/Dockerfile ./src/"$COMPONENT"/ > "$BUILD_LOG" + elif [ "$COMPONENT" == "pathcomp" ]; then + BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-frontend.log" + $DOCKER_BUILD -t "$COMPONENT-frontend:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/frontend/Dockerfile . > "$BUILD_LOG" - if [ "$COMPONENT" == "pathcomp" ]; then - IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-frontend:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') + BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-backend.log" + $DOCKER_BUILD -t "$COMPONENT-backend:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/backend/Dockerfile . > "$BUILD_LOG" + # next command is redundant, but helpful to keep cache updated between rebuilds + IMAGE_NAME="$COMPONENT-backend:$TFS_IMAGE_TAG-builder" + $DOCKER_BUILD -t "$IMAGE_NAME" --target builder -f ./src/"$COMPONENT"/backend/Dockerfile . >> "$BUILD_LOG" + elif [ "$COMPONENT" == "dlt" ]; then + BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-connector.log" + $DOCKER_BUILD -t "$COMPONENT-connector:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/connector/Dockerfile . > "$BUILD_LOG" - TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-frontend.log" - docker tag "$COMPONENT-frontend:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" + BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-gateway.log" + $DOCKER_BUILD -t "$COMPONENT-gateway:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/gateway/Dockerfile . > "$BUILD_LOG" + else + $DOCKER_BUILD -t "$COMPONENT:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/Dockerfile . > "$BUILD_LOG" + fi - PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-frontend.log" - docker push "$IMAGE_URL" > "$PUSH_LOG" + echo " Pushing Docker image to '$TFS_REGISTRY_IMAGES'..." - IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-backend:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') + if [ "$COMPONENT" == "pathcomp" ]; then + IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-frontend:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') - TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-backend.log" - docker tag "$COMPONENT-backend:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" + TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-frontend.log" + docker tag "$COMPONENT-frontend:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" - PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-backend.log" - docker push "$IMAGE_URL" > "$PUSH_LOG" - elif [ "$COMPONENT" == "dlt" ]; then - IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-connector:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') + PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-frontend.log" + docker push "$IMAGE_URL" > "$PUSH_LOG" - TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-connector.log" - docker tag "$COMPONENT-connector:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" + IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-backend:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') - PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-connector.log" - docker push "$IMAGE_URL" > "$PUSH_LOG" + TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-backend.log" + docker tag "$COMPONENT-backend:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" - IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-gateway:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') + PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-backend.log" + docker push "$IMAGE_URL" > "$PUSH_LOG" + elif [ "$COMPONENT" == "dlt" ]; then + IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-connector:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') - TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-gateway.log" - docker tag "$COMPONENT-gateway:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" + TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-connector.log" + docker tag "$COMPONENT-connector:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" - PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-gateway.log" - docker push "$IMAGE_URL" > "$PUSH_LOG" - else - IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') + PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-connector.log" + docker push "$IMAGE_URL" > "$PUSH_LOG" - TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}.log" - docker tag "$COMPONENT:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" + IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-gateway:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') - PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}.log" - docker push "$IMAGE_URL" > "$PUSH_LOG" + TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-gateway.log" + docker tag "$COMPONENT-gateway:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" + + PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-gateway.log" + docker push "$IMAGE_URL" > "$PUSH_LOG" + else + IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') + + TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}.log" + docker tag "$COMPONENT:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" + + PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}.log" + docker push "$IMAGE_URL" > "$PUSH_LOG" + fi + else + echo " Skipping Docker build for '$COMPONENT' as the image already exists and TFS_BUILD_IF_EXISTS is set to 'NO'." fi fi -- GitLab From 9d0c65cb6f1de4e136ff39d85bf817bbdf904353 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 15:20:31 +0200 Subject: [PATCH 08/94] Updated the Dockerfile of DLT to work with general deployment script. --- src/dlt/gateway/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile index dcbd81810..dae745d9f 100644 --- a/src/dlt/gateway/Dockerfile +++ b/src/dlt/gateway/Dockerfile @@ -5,15 +5,15 @@ FROM node:20 WORKDIR /usr/dltApp # Copy package.json and package-lock.json -COPY dltApp/package*.json ./ +COPY src/dlt/connector/gateway/dltApp/dltApp/package*.json ./ # Copy tsconfig.json -COPY dltApp/tsconfig*.json ./ +COPY src/dlt/connector/gateway/dltApp/tsconfig*.json ./ # Copy the proto folder -COPY dltApp/proto/ ./proto +COPY src/dlt/connector/gateway/dltApp/dltApp/proto/ ./proto # Copy the src folder -COPY dltApp/src/ ./src +COPY src/dlt/connector/gateway/dltApp/dltApp/src/ ./src # Install dependencies RUN npm install -- GitLab From 50b7bf4e28c9e2407d878a188912721bc91d606e Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 15:24:46 +0200 Subject: [PATCH 09/94] Crroected some path errors --- src/dlt/gateway/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile index dae745d9f..4c911f53e 100644 --- a/src/dlt/gateway/Dockerfile +++ b/src/dlt/gateway/Dockerfile @@ -5,15 +5,15 @@ FROM node:20 WORKDIR /usr/dltApp # Copy package.json and package-lock.json -COPY src/dlt/connector/gateway/dltApp/dltApp/package*.json ./ +COPY src/dlt/gateway/dltApp/package*.json ./ # Copy tsconfig.json -COPY src/dlt/connector/gateway/dltApp/tsconfig*.json ./ +COPY src/dlt/gateway/dltApp/tsconfig*.json ./ # Copy the proto folder -COPY src/dlt/connector/gateway/dltApp/dltApp/proto/ ./proto +COPY src/dlt/gateway/dltApp/proto/ ./proto # Copy the src folder -COPY src/dlt/connector/gateway/dltApp/dltApp/src/ ./src +COPY src/dlt/gateway/dltApp/src/ ./src # Install dependencies RUN npm install -- GitLab From 4ca50c44690214601e8f112db4013b7d5717f2ad Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 15:35:49 +0200 Subject: [PATCH 10/94] Corrected the configurationMap --- manifests/dltservice.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index 5362fe6ab..de79ff86b 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -16,7 +16,6 @@ apiVersion: v1 kind: ConfigMap metadata: name: dlt-config - namespace: dlt data: CHANNEL_NAME: "channel1" CHAINCODE_NAME: "adrenalineDLT" @@ -34,7 +33,6 @@ apiVersion: apps/v1 kind: Deployment metadata: name: dltservice - namespace: dlt spec: selector: matchLabels: @@ -146,7 +144,6 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: name: dlt-pvc - namespace: dlt spec: accessModes: - ReadOnlyMany @@ -169,7 +166,6 @@ spec: hostPath: path: "/home/cttc/test-network" claimRef: - namespace: dlt name: dlt-pvc --- @@ -178,7 +174,6 @@ apiVersion: v1 kind: Service metadata: name: gatewayservice - namespace: dlt spec: selector: app: dltservice @@ -195,7 +190,6 @@ apiVersion: v1 kind: Service metadata: name: dltservice - namespace: dlt labels: app: dltservice spec: -- GitLab From 21b72b8cf04511d071e286bb18190320bc943890 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 15:44:26 +0200 Subject: [PATCH 11/94] PEER_ENDPOINT value. --- manifests/dltservice.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index de79ff86b..fd892df87 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -20,7 +20,7 @@ data: CHANNEL_NAME: "channel1" CHAINCODE_NAME: "adrenalineDLT" MSP_ID: "Org1MSP" - PEER_ENDPOINT: "PEER_IP:PORT" + PEER_ENDPOINT: "10.1.1.96:7051" #Change to required peer# PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" CRYPTO_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com" KEY_DIRECTORY_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/keystore" -- GitLab From 94682324f85172731ae464026ed7b92c795d9a07 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 16:20:55 +0200 Subject: [PATCH 12/94] Corrected HostPath. --- manifests/dltservice.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index fd892df87..4c5cc9907 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -164,7 +164,7 @@ spec: - ReadOnlyMany persistentVolumeReclaimPolicy: Retain hostPath: - path: "/home/cttc/test-network" + path: "/home/cttc/fabric-samples/test-network" claimRef: name: dlt-pvc -- GitLab From 55392f7aabf2d7af0fdc37e426a19bfa1e041ab7 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 16:49:44 +0200 Subject: [PATCH 13/94] Manifest update --- manifests/dltservice.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index 4c5cc9907..b8bc1a833 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -164,7 +164,7 @@ spec: - ReadOnlyMany persistentVolumeReclaimPolicy: Retain hostPath: - path: "/home/cttc/fabric-samples/test-network" + path: "/home/ubuntu/fabric-samples/test-network" #Update to correct host paths where the MSP is located. claimRef: name: dlt-pvc -- GitLab From 84292923f68ed751a5be0c0664769082c316c774 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 17:58:34 +0200 Subject: [PATCH 14/94] Updated deployment to use secrets for the HLF certificates --- deploy/tfs.sh | 26 +++++++++++++++++++++++++ manifests/dltservice.yaml | 39 +++++++++++++++++++++++--------------- src/dlt/gateway/Dockerfile | 3 ++- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/deploy/tfs.sh b/deploy/tfs.sh index e81b946c8..4c187b9a4 100755 --- a/deploy/tfs.sh +++ b/deploy/tfs.sh @@ -118,6 +118,19 @@ export PROM_EXT_PORT_HTTP=${PROM_EXT_PORT_HTTP:-"9090"} # If not already set, set the external port Grafana HTTP Dashboards will be exposed to. export GRAF_EXT_PORT_HTTP=${GRAF_EXT_PORT_HTTP:-"3000"} +# ----- HLF Key Paths ----------------------------------------------------------- + +echo "Create secret for keystore" +KEY_DIRECTORY_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/keystore" +printf "\n" + +echo "Create secret for signcerts" +CERT_DIRECTORY_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/signcerts" +printf "\n" + +echo "Create secret for ca.crt" +TLS_CERT_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/peers/peer0.org1.adrenaline.com/tls/ca.crt" +printf "\n" ######################################################################################################################## # Automated steps start here @@ -178,6 +191,19 @@ kubectl create secret generic qdb-data --namespace ${TFS_K8S_NAMESPACE} --type=' --from-literal=METRICSDB_PASSWORD=${QDB_PASSWORD} printf "\n" +echo "Create secret for HLF keystore" +kubectl create secret generic dlt-keystone --namespace ${TFS_K8S_NAMESPACE} --from-file=keystore=${KEY_DIRECTORY_PATH} +printf "\n" + +echo "Create secret for HLF signcerts" +kubectl create secret generic dlt-signcerts --namespace ${TFS_K8S_NAMESPACE} --from-file=signcerts=${CERT_DIRECTORY_PATH} +printf "\n" + +echo "Create secret for HLF ca.crt" +kubectl create secret generic dlt-ca-crt --namespace ${TFS_K8S_NAMESPACE} --from-file=ca.crt=${TLS_CERT_PATH} +printf "\n" + + echo "Deploying components and collecting environment variables..." ENV_VARS_SCRIPT=tfs_runtime_env_vars.sh echo "# Environment variables for TeraFlowSDN deployment" > $ENV_VARS_SCRIPT diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index b8bc1a833..d3bd6c436 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -23,9 +23,9 @@ data: PEER_ENDPOINT: "10.1.1.96:7051" #Change to required peer# PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" CRYPTO_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com" - KEY_DIRECTORY_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/keystore" - CERT_DIRECTORY_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/signcerts" - TLS_CERT_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/peers/peer0.org1.adrenaline.com/tls/ca.crt" + KEY_DIRECTORY_PATH: "/etc/hyperledger/fabric-keystore" + CERT_DIRECTORY_PATH: "/etc/hyperledger/fabric-signcerts" + TLS_CERT_PATH: "/etc/hyperledger/fabric-ca-crt/ca.crt" --- @@ -87,6 +87,15 @@ spec: - mountPath: /test-network name: dlt-volume readOnly: true + - name: keystore + mountPath: /etc/hyperledger/fabric-keystore + readOnly: true + - name: signcerts + mountPath: /etc/hyperledger/fabric-signcerts + readOnly: true + - name: ca-crt + mountPath: /etc/hyperledger/fabric-ca-crt + readOnly: true env: - name: CHANNEL_NAME valueFrom: @@ -119,24 +128,24 @@ spec: name: dlt-config key: CRYPTO_PATH - name: KEY_DIRECTORY_PATH - valueFrom: - configMapKeyRef: - name: dlt-config - key: KEY_DIRECTORY_PATH + value: "/etc/hyperledger/fabric-keystore" - name: CERT_DIRECTORY_PATH - valueFrom: - configMapKeyRef: - name: dlt-config - key: CERT_DIRECTORY_PATH + value: "/etc/hyperledger/fabric-signcerts" - name: TLS_CERT_PATH - valueFrom: - configMapKeyRef: - name: dlt-config - key: TLS_CERT_PATH + value: "/etc/hyperledger/fabric-ca-crt/ca.crt" volumes: - name: dlt-volume persistentVolumeClaim: claimName: dlt-pvc + - name: keystore + secret: + secretName: dlt-keystone + - name: signcerts + secret: + secretName: dlt-signcerts + - name: ca-crt + secret: + secretName: dlt-ca-cr --- diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile index 4c911f53e..1f2b4abed 100644 --- a/src/dlt/gateway/Dockerfile +++ b/src/dlt/gateway/Dockerfile @@ -24,4 +24,5 @@ RUN npm install EXPOSE 50051 # Command to run the service -CMD ["node", "src/dltGateway.js"] \ No newline at end of file +#CMD ["node", "src/dltGateway.js"] +CMD ["sh", "-c", "sleep 3600"] # Keep the container running for testing -- GitLab From 6bd936c17ac6eba36f8127ecdc160680cbaae2ad Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 18:09:05 +0200 Subject: [PATCH 15/94] Updated Deployment Scripts to use Secrets --- deploy/tfs.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/tfs.sh b/deploy/tfs.sh index 4c187b9a4..a11dee682 100755 --- a/deploy/tfs.sh +++ b/deploy/tfs.sh @@ -120,15 +120,15 @@ export GRAF_EXT_PORT_HTTP=${GRAF_EXT_PORT_HTTP:-"3000"} # ----- HLF Key Paths ----------------------------------------------------------- -echo "Create secret for keystore" -KEY_DIRECTORY_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/keystore" +echo "Keystore PATH" +KEY_DIRECTORY_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/keystore/priv_sk" printf "\n" -echo "Create secret for signcerts" -CERT_DIRECTORY_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/signcerts" +echo "signcerts PATH" +CERT_DIRECTORY_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/signcerts/User1@org1.adrenaline.com-cert.pem" printf "\n" -echo "Create secret for ca.crt" +echo "ca.crt PATH" TLS_CERT_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/peers/peer0.org1.adrenaline.com/tls/ca.crt" printf "\n" -- GitLab From b6bd5b20cd40ff663fc0cda11a5fd36d8981863c Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 18:19:20 +0200 Subject: [PATCH 16/94] Updated Manifest for DLT --- manifests/dltservice.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index d3bd6c436..8bc435b39 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -145,7 +145,7 @@ spec: secretName: dlt-signcerts - name: ca-crt secret: - secretName: dlt-ca-cr + secretName: dlt-ca-crt --- -- GitLab From cc538beb0dfabfbf3e3539bdb766d81d6df229b8 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 28 Jun 2024 18:29:02 +0200 Subject: [PATCH 17/94] Updated Dockerfile of DLT gateway --- src/dlt/gateway/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile index 1f2b4abed..694cc521f 100644 --- a/src/dlt/gateway/Dockerfile +++ b/src/dlt/gateway/Dockerfile @@ -24,5 +24,5 @@ RUN npm install EXPOSE 50051 # Command to run the service -#CMD ["node", "src/dltGateway.js"] -CMD ["sh", "-c", "sleep 3600"] # Keep the container running for testing +CMD ["node", "src/dltGateway.js"] +#CMD ["sh", "-c", "sleep 3600"] # Keep the container running for testing -- GitLab From f7af99017cb315ad493522a39eb1112510a45b2f Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 1 Jul 2024 08:51:19 +0200 Subject: [PATCH 18/94] Debugging --- src/dlt/gateway/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile index 694cc521f..1f2b4abed 100644 --- a/src/dlt/gateway/Dockerfile +++ b/src/dlt/gateway/Dockerfile @@ -24,5 +24,5 @@ RUN npm install EXPOSE 50051 # Command to run the service -CMD ["node", "src/dltGateway.js"] -#CMD ["sh", "-c", "sleep 3600"] # Keep the container running for testing +#CMD ["node", "src/dltGateway.js"] +CMD ["sh", "-c", "sleep 3600"] # Keep the container running for testing -- GitLab From 1e8f51ac7f59a8ad2f853a332ab5cafbf817391e Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 1 Jul 2024 10:05:55 +0200 Subject: [PATCH 19/94] Debugging --- src/dlt/gateway/dltApp/src/fabricConnect.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dlt/gateway/dltApp/src/fabricConnect.ts b/src/dlt/gateway/dltApp/src/fabricConnect.ts index 51c8f8046..42711f5a0 100644 --- a/src/dlt/gateway/dltApp/src/fabricConnect.ts +++ b/src/dlt/gateway/dltApp/src/fabricConnect.ts @@ -105,6 +105,7 @@ async function newGrpcConnection(): Promise { async function newIdentity(): Promise { const certPath = await getFirstDirFileName(certDirectoryPath); + console.log("DEBUG", certPath); const credentials = await fs.readFile(certPath); return { mspId, credentials }; } @@ -116,6 +117,7 @@ async function getFirstDirFileName(dirPath: string): Promise { async function newSigner(): Promise { const keyPath = await getFirstDirFileName(keyDirectoryPath); + console.log("DEBUG2", keyPath); const privateKeyPem = await fs.readFile(keyPath); const privateKey = crypto.createPrivateKey(privateKeyPem); return signers.newPrivateKeySigner(privateKey); -- GitLab From 8aed033c5a3757ffe3b21ac616f8d8163a0fda34 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 1 Jul 2024 10:19:57 +0200 Subject: [PATCH 20/94] debugging --- deploy/tfs.sh | 2 +- src/dlt/gateway/Dockerfile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/tfs.sh b/deploy/tfs.sh index a11dee682..6626262f8 100755 --- a/deploy/tfs.sh +++ b/deploy/tfs.sh @@ -196,7 +196,7 @@ kubectl create secret generic dlt-keystone --namespace ${TFS_K8S_NAMESPACE} --fr printf "\n" echo "Create secret for HLF signcerts" -kubectl create secret generic dlt-signcerts --namespace ${TFS_K8S_NAMESPACE} --from-file=signcerts=${CERT_DIRECTORY_PATH} +kubectl create secret generic dlt-signcerts --namespace ${TFS_K8S_NAMESPACE} --from-file=signcerts.pem=${CERT_DIRECTORY_PATH} printf "\n" echo "Create secret for HLF ca.crt" diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile index 1f2b4abed..694cc521f 100644 --- a/src/dlt/gateway/Dockerfile +++ b/src/dlt/gateway/Dockerfile @@ -24,5 +24,5 @@ RUN npm install EXPOSE 50051 # Command to run the service -#CMD ["node", "src/dltGateway.js"] -CMD ["sh", "-c", "sleep 3600"] # Keep the container running for testing +CMD ["node", "src/dltGateway.js"] +#CMD ["sh", "-c", "sleep 3600"] # Keep the container running for testing -- GitLab From 547c5f6ad4ac117fb32c73b5be7da724305492a4 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 1 Jul 2024 12:07:21 +0200 Subject: [PATCH 21/94] Debugging --- manifests/dltservice.yaml | 8 ++++---- src/dlt/gateway/dltApp/src/fabricConnect.ts | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index 8bc435b39..6602a45f5 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -23,8 +23,8 @@ data: PEER_ENDPOINT: "10.1.1.96:7051" #Change to required peer# PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" CRYPTO_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com" - KEY_DIRECTORY_PATH: "/etc/hyperledger/fabric-keystore" - CERT_DIRECTORY_PATH: "/etc/hyperledger/fabric-signcerts" + KEY_DIRECTORY_PATH: "/etc/hyperledger/fabric-keystore/keystore" + CERT_DIRECTORY_PATH: "/etc/hyperledger/fabric-signcerts/signcerts.pem" TLS_CERT_PATH: "/etc/hyperledger/fabric-ca-crt/ca.crt" --- @@ -128,9 +128,9 @@ spec: name: dlt-config key: CRYPTO_PATH - name: KEY_DIRECTORY_PATH - value: "/etc/hyperledger/fabric-keystore" + value: "/etc/hyperledger/fabric-keystore/keystore" - name: CERT_DIRECTORY_PATH - value: "/etc/hyperledger/fabric-signcerts" + value: "/etc/hyperledger/fabric-signcerts/signcerts.pem" - name: TLS_CERT_PATH value: "/etc/hyperledger/fabric-ca-crt/ca.crt" volumes: diff --git a/src/dlt/gateway/dltApp/src/fabricConnect.ts b/src/dlt/gateway/dltApp/src/fabricConnect.ts index 42711f5a0..ae61ef2ff 100644 --- a/src/dlt/gateway/dltApp/src/fabricConnect.ts +++ b/src/dlt/gateway/dltApp/src/fabricConnect.ts @@ -112,7 +112,9 @@ async function newIdentity(): Promise { async function getFirstDirFileName(dirPath: string): Promise { const files = await fs.readdir(dirPath); - return path.join(dirPath, files[0]); + const filePath = path.join(dirPath, files[0]); + const realFilePath = await fs.readlink(filePath); + return path.join(dirPath, realFilePath); } async function newSigner(): Promise { -- GitLab From 3f9d67757abc4dc8cfbcc136bf025804d255135d Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 1 Jul 2024 12:27:15 +0200 Subject: [PATCH 22/94] Debugging --- manifests/dltservice.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index 6602a45f5..a6b04b4d8 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -23,8 +23,8 @@ data: PEER_ENDPOINT: "10.1.1.96:7051" #Change to required peer# PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" CRYPTO_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com" - KEY_DIRECTORY_PATH: "/etc/hyperledger/fabric-keystore/keystore" - CERT_DIRECTORY_PATH: "/etc/hyperledger/fabric-signcerts/signcerts.pem" + KEY_DIRECTORY_PATH: "/etc/hyperledger/fabric-keystore/" + CERT_DIRECTORY_PATH: "/etc/hyperledger/fabric-signcerts/" TLS_CERT_PATH: "/etc/hyperledger/fabric-ca-crt/ca.crt" --- @@ -128,9 +128,9 @@ spec: name: dlt-config key: CRYPTO_PATH - name: KEY_DIRECTORY_PATH - value: "/etc/hyperledger/fabric-keystore/keystore" + value: "/etc/hyperledger/fabric-keystore/" - name: CERT_DIRECTORY_PATH - value: "/etc/hyperledger/fabric-signcerts/signcerts.pem" + value: "/etc/hyperledger/fabric-signcerts/" - name: TLS_CERT_PATH value: "/etc/hyperledger/fabric-ca-crt/ca.crt" volumes: -- GitLab From 65839d40fb45f514eb876433cb0f0675accc6dbf Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 1 Jul 2024 12:33:07 +0200 Subject: [PATCH 23/94] Debugging --- manifests/dltservice.yaml | 8 +++---- src/dlt/gateway/dltApp/src/fabricConnect.ts | 24 ++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index a6b04b4d8..6602a45f5 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -23,8 +23,8 @@ data: PEER_ENDPOINT: "10.1.1.96:7051" #Change to required peer# PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" CRYPTO_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com" - KEY_DIRECTORY_PATH: "/etc/hyperledger/fabric-keystore/" - CERT_DIRECTORY_PATH: "/etc/hyperledger/fabric-signcerts/" + KEY_DIRECTORY_PATH: "/etc/hyperledger/fabric-keystore/keystore" + CERT_DIRECTORY_PATH: "/etc/hyperledger/fabric-signcerts/signcerts.pem" TLS_CERT_PATH: "/etc/hyperledger/fabric-ca-crt/ca.crt" --- @@ -128,9 +128,9 @@ spec: name: dlt-config key: CRYPTO_PATH - name: KEY_DIRECTORY_PATH - value: "/etc/hyperledger/fabric-keystore/" + value: "/etc/hyperledger/fabric-keystore/keystore" - name: CERT_DIRECTORY_PATH - value: "/etc/hyperledger/fabric-signcerts/" + value: "/etc/hyperledger/fabric-signcerts/signcerts.pem" - name: TLS_CERT_PATH value: "/etc/hyperledger/fabric-ca-crt/ca.crt" volumes: diff --git a/src/dlt/gateway/dltApp/src/fabricConnect.ts b/src/dlt/gateway/dltApp/src/fabricConnect.ts index ae61ef2ff..cab955958 100644 --- a/src/dlt/gateway/dltApp/src/fabricConnect.ts +++ b/src/dlt/gateway/dltApp/src/fabricConnect.ts @@ -104,23 +104,23 @@ async function newGrpcConnection(): Promise { } async function newIdentity(): Promise { - const certPath = await getFirstDirFileName(certDirectoryPath); - console.log("DEBUG", certPath); - const credentials = await fs.readFile(certPath); + //const certPath = await getFirstDirFileName(certDirectoryPath); + console.log("DEBUG", certDirectoryPath); + const credentials = await fs.readFile(certDirectoryPath); return { mspId, credentials }; } -async function getFirstDirFileName(dirPath: string): Promise { - const files = await fs.readdir(dirPath); - const filePath = path.join(dirPath, files[0]); - const realFilePath = await fs.readlink(filePath); - return path.join(dirPath, realFilePath); -} +//async function getFirstDirFileName(dirPath: string): Promise { + // const files = await fs.readdir(dirPath); + // const filePath = path.join(dirPath, files[0]); + // const realFilePath = await fs.readlink(filePath); + // return path.join(dirPath, realFilePath); +//} async function newSigner(): Promise { - const keyPath = await getFirstDirFileName(keyDirectoryPath); - console.log("DEBUG2", keyPath); - const privateKeyPem = await fs.readFile(keyPath); + //const keyPath = await getFirstDirFileName(keyDirectoryPath); + console.log("DEBUG2", keyDirectoryPath); + const privateKeyPem = await fs.readFile(keyDirectoryPath); const privateKey = crypto.createPrivateKey(privateKeyPem); return signers.newPrivateKeySigner(privateKey); } -- GitLab From 179e98bc782bf4f0f9b53bd739d3e0b71333ce90 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 1 Jul 2024 12:52:18 +0200 Subject: [PATCH 24/94] Debugging --- src/dlt/gateway/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile index 694cc521f..1f2b4abed 100644 --- a/src/dlt/gateway/Dockerfile +++ b/src/dlt/gateway/Dockerfile @@ -24,5 +24,5 @@ RUN npm install EXPOSE 50051 # Command to run the service -CMD ["node", "src/dltGateway.js"] -#CMD ["sh", "-c", "sleep 3600"] # Keep the container running for testing +#CMD ["node", "src/dltGateway.js"] +CMD ["sh", "-c", "sleep 3600"] # Keep the container running for testing -- GitLab From 638299bd338234187854d49e000a9ed80436d454 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 1 Jul 2024 14:45:31 +0200 Subject: [PATCH 25/94] Debugging --- src/dlt/gateway/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile index 1f2b4abed..694cc521f 100644 --- a/src/dlt/gateway/Dockerfile +++ b/src/dlt/gateway/Dockerfile @@ -24,5 +24,5 @@ RUN npm install EXPOSE 50051 # Command to run the service -#CMD ["node", "src/dltGateway.js"] -CMD ["sh", "-c", "sleep 3600"] # Keep the container running for testing +CMD ["node", "src/dltGateway.js"] +#CMD ["sh", "-c", "sleep 3600"] # Keep the container running for testing -- GitLab From 3470151e45fd89cb4a074195987b39b48ee05b82 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 1 Jul 2024 14:51:41 +0200 Subject: [PATCH 26/94] Debugging --- deploy/tfs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/tfs.sh b/deploy/tfs.sh index 6626262f8..00bc2d48f 100755 --- a/deploy/tfs.sh +++ b/deploy/tfs.sh @@ -47,7 +47,7 @@ export TFS_SKIP_BUILD=${TFS_SKIP_BUILD:-""} # If not already set, disable build-if-exists flag to skip building Docker images if they already exist. # If TFS_BUILD_IF_EXISTS is "NO", the containers are not rebuilt if they already exist. -export TFS_BUILD_IF_EXISTS=${TFS_BUILD_IF_EXISTS:-"NO"} +export TFS_BUILD_IF_EXISTS=${TFS_BUILD_IF_EXISTS:-"YES"} # ----- CockroachDB ------------------------------------------------------------ -- GitLab From 130537bcd9b71c7e3e880458a79b7426f5380099 Mon Sep 17 00:00:00 2001 From: diazjj Date: Tue, 9 Jul 2024 10:53:00 +0200 Subject: [PATCH 27/94] Updated event handling on dltGateway --- src/dlt/gateway/dltApp/src/dltGateway.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dlt/gateway/dltApp/src/dltGateway.js b/src/dlt/gateway/dltApp/src/dltGateway.js index 8c9e79663..eee51f355 100644 --- a/src/dlt/gateway/dltApp/src/dltGateway.js +++ b/src/dlt/gateway/dltApp/src/dltGateway.js @@ -170,8 +170,7 @@ function subscribeToDlt(call) { domain_uuid: { uuid: eventJson.record_id.domain_uuid.uuid }, type: eventJson.record_id.type || 'DLTRECORDTYPE_UNDEFINED', record_uuid: { uuid: eventJson.record_id.record_uuid.uuid } - }, - data_json: JSON.stringify(eventJson) + } }); // Check if the internal buffer is full -- GitLab From f4fb7d70154b408513ed5e10496be836c05ef286 Mon Sep 17 00:00:00 2001 From: diazjj Date: Tue, 9 Jul 2024 10:56:47 +0200 Subject: [PATCH 28/94] Updated code of gRPC server --- src/dlt/gateway/dltApp/src/dltGateway.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dlt/gateway/dltApp/src/dltGateway.js b/src/dlt/gateway/dltApp/src/dltGateway.js index eee51f355..f0ca8120b 100644 --- a/src/dlt/gateway/dltApp/src/dltGateway.js +++ b/src/dlt/gateway/dltApp/src/dltGateway.js @@ -225,7 +225,7 @@ server.bindAsync(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure(), asy } console.log(`gRPC server running at http://0.0.0.0:${PORT}`); await initChaincodeConnection(); //Connects to the chaincode and - server.start(); + server; }); // Handle shutdown gracefully -- GitLab From ed1af500517373fc8899153dafff348e8701a33b Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 25 Jul 2024 11:03:47 +0200 Subject: [PATCH 29/94] Integrating DLT into Interdomain module --- my_deploy.sh | 2 +- src/interdomain/Config.py | 7 + src/interdomain/service/__main__.py | 25 ++-- .../topology_abstractor/DltRecorder.py | 133 ++++++++++++++++++ 4 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 src/interdomain/service/topology_abstractor/DltRecorder.py diff --git a/my_deploy.sh b/my_deploy.sh index d18c5db7f..c0f2196c8 100755 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -20,7 +20,7 @@ export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/" # Set the list of components, separated by spaces, you want to build images for, and deploy. -export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_generator dlt" +export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_generator interdomain dlt" # Uncomment to activate Monitoring #export TFS_COMPONENTS="${TFS_COMPONENTS} monitoring" diff --git a/src/interdomain/Config.py b/src/interdomain/Config.py index 918f60d79..d9098447b 100644 --- a/src/interdomain/Config.py +++ b/src/interdomain/Config.py @@ -15,6 +15,7 @@ from common.Settings import get_setting SETTING_NAME_TOPOLOGY_ABSTRACTOR = 'TOPOLOGY_ABSTRACTOR' +SETTING_NAME_DLT_INTEGRATION = 'DLT_INTEGRATION' TRUE_VALUES = {'Y', 'YES', 'TRUE', 'T', 'E', 'ENABLE', 'ENABLED'} def is_topology_abstractor_enabled() -> bool: @@ -22,3 +23,9 @@ def is_topology_abstractor_enabled() -> bool: if is_enabled is None: return False str_is_enabled = str(is_enabled).upper() return str_is_enabled in TRUE_VALUES + +def is_dlt_enabled() -> bool: + is_enabled = get_setting(SETTING_NAME_DLT_INTEGRATION, default=None) + if is_enabled is None: return False + str_is_enabled = str(is_enabled).upper() + return str_is_enabled in TRUE_VALUES diff --git a/src/interdomain/service/__main__.py b/src/interdomain/service/__main__.py index c0497bd29..a0a25ae1e 100644 --- a/src/interdomain/service/__main__.py +++ b/src/interdomain/service/__main__.py @@ -18,8 +18,9 @@ from common.Constants import ServiceNameEnum from common.Settings import ( ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, get_env_var_name, get_log_level, get_metrics_port, wait_for_environment_variables) -from interdomain.Config import is_topology_abstractor_enabled -from .topology_abstractor.TopologyAbstractor import TopologyAbstractor +from interdomain.Config import is_dlt_enabled +#from .topology_abstractor.TopologyAbstractor import TopologyAbstractor +from .topology_abstractor.DltRecorder import DLTRecorder from .InterdomainService import InterdomainService from .RemoteDomainClients import RemoteDomainClients @@ -64,17 +65,25 @@ def main(): grpc_service.start() # Subscribe to Context Events - topology_abstractor_enabled = is_topology_abstractor_enabled() - if topology_abstractor_enabled: - topology_abstractor = TopologyAbstractor() - topology_abstractor.start() + # topology_abstractor_enabled = is_topology_abstractor_enabled() + # if topology_abstractor_enabled: + # topology_abstractor = TopologyAbstractor() + # topology_abstractor.start() + + # Subscribe to Context Events + dlt_enabled = is_dlt_enabled() + if dlt_enabled: + dlt_recorder = DLTRecorder() + dlt_recorder.start() # Wait for Ctrl+C or termination signal while not terminate.wait(timeout=1.0): pass LOGGER.info('Terminating...') - if topology_abstractor_enabled: - topology_abstractor.stop() + # if topology_abstractor_enabled: + # topology_abstractor.stop() + if dlt_enabled: + dlt_recorder.stop() grpc_service.stop() remote_domain_clients.stop() diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py new file mode 100644 index 000000000..cb8c194eb --- /dev/null +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -0,0 +1,133 @@ +import logging +import threading +from typing import Dict, Optional + +from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum +from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name +from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkEvent, TopologyId, TopologyEvent +from common.tools.context_queries.Context import create_context +from common.tools.context_queries.Device import get_uuids_of_devices_in_topology +from common.tools.context_queries.Topology import create_missing_topologies +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Context import json_context_id +from common.tools.object_factory.Device import json_device_id +from common.tools.object_factory.Topology import json_topology_id +from context.client.ContextClient import ContextClient +from context.client.EventsCollector import EventsCollector +from dlt.connector.client.DltConnectorClient import DltConnectorClient +from .DltRecordSender import DltRecordSender +from .Types import EventTypes + +LOGGER = logging.getLogger(__name__) + +ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) +INTERDOMAIN_TOPOLOGY_ID = TopologyId(**json_topology_id(INTERDOMAIN_TOPOLOGY_NAME, context_id=ADMIN_CONTEXT_ID)) + + +class DLTRecorder(threading.Thread): + def __init__(self) -> None: + super().__init__(daemon=True) + self.terminate = threading.Event() + self.context_client = ContextClient() + self.context_event_collector = EventsCollector(self.context_client) + + def stop(self): + self.terminate.set() + + def run(self) -> None: + self.context_client.connect() + create_context(self.context_client, DEFAULT_CONTEXT_NAME) + self.create_topologies() + self.context_event_collector.start() + + while not self.terminate.is_set(): + event = self.context_event_collector.get_event(timeout=0.1) + if event is None: + continue + LOGGER.info('Processing Event({:s})...'.format(grpc_message_to_json_string(event))) + self.update_record(event) + + self.context_event_collector.stop() + self.context_client.close() + + def create_topologies(self): + topology_uuids = [DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME] + create_missing_topologies(self.context_client, ADMIN_CONTEXT_ID, topology_uuids) + + def get_dlt_connector_client(self) -> Optional[DltConnectorClient]: + env_vars = find_environment_variables([ + get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_HOST), + get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + ]) + if len(env_vars) == 2: + dlt_connector_client = DltConnectorClient() + dlt_connector_client.connect() + return dlt_connector_client + return None + + def update_record(self, event: EventTypes) -> None: + dlt_connector_client = self.get_dlt_connector_client() + dlt_record_sender = DltRecordSender(self.context_client, dlt_connector_client) + + if isinstance(event, ContextEvent): + LOGGER.debug('Processing ContextEvent({:s})'.format(grpc_message_to_json_string(event))) + LOGGER.warning('Ignoring ContextEvent({:s})'.format(grpc_message_to_json_string(event))) + + elif isinstance(event, TopologyEvent): + LOGGER.debug('Processing TopologyEvent({:s})'.format(grpc_message_to_json_string(event))) + self.process_topology_event(event, dlt_record_sender) + + elif isinstance(event, DeviceEvent): + LOGGER.debug('Processing DeviceEvent({:s})'.format(grpc_message_to_json_string(event))) + self.process_device_event(event, dlt_record_sender) + + elif isinstance(event, LinkEvent): + LOGGER.debug('Processing LinkEvent({:s})'.format(grpc_message_to_json_string(event))) + self.process_link_event(event, dlt_record_sender) + + else: + LOGGER.warning('Unsupported Event({:s})'.format(grpc_message_to_json_string(event))) + + dlt_record_sender.commit() + if dlt_connector_client is not None: + dlt_connector_client.close() + + def process_topology_event(self, event: TopologyEvent, dlt_record_sender: DltRecordSender) -> None: + topology_id = event.topology_id + topology_uuid = topology_id.topology_uuid.uuid + context_id = topology_id.context_id + context_uuid = context_id.context_uuid.uuid + topology_uuids = {DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME} + + context = self.context_client.GetContext(context_id) + context_name = context.name + + topology_details = self.context_client.GetTopologyDetails(topology_id) + topology_name = topology_details.name + + if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ + (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): + for device in topology_details.devices: + dlt_record_sender.add_device(topology_id, device) + + for link in topology_details.links: + dlt_record_sender.add_link(topology_id, link) + + else: + MSG = 'Ignoring ({:s}/{:s})({:s}/{:s}) TopologyEvent({:s})' + args = context_uuid, context_name, topology_uuid, topology_name, grpc_message_to_json_string(event) + LOGGER.warning(MSG.format(*args)) + + +#Check which ID to use. + + def process_device_event(self, event: DeviceEvent, dlt_record_sender: DltRecordSender) -> None: + device_id = event.device_id + device_uuid = device_id.device_uuid.uuid + device = self.context_client.GetDevice(device_id) + dlt_record_sender.add_device(device_id.context_id, device) + + def process_link_event(self, event: LinkEvent, dlt_record_sender: DltRecordSender) -> None: + link_id = event.link_id + link = self.context_client.GetLink(link_id) + dlt_record_sender.add_link(link_id.context_id, link) -- GitLab From 9e81b87e3bc2f6f52fec082c9b04b534757836f0 Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 25 Jul 2024 11:14:01 +0200 Subject: [PATCH 30/94] Debugging --- src/interdomain/service/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/interdomain/service/__main__.py b/src/interdomain/service/__main__.py index a0a25ae1e..577af4908 100644 --- a/src/interdomain/service/__main__.py +++ b/src/interdomain/service/__main__.py @@ -73,6 +73,7 @@ def main(): # Subscribe to Context Events dlt_enabled = is_dlt_enabled() if dlt_enabled: + LOGGER.info('Starting DLT functionality...') dlt_recorder = DLTRecorder() dlt_recorder.start() -- GitLab From 15b795acc9266baf48aab0125de054dd9b715d1d Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 25 Jul 2024 11:26:19 +0200 Subject: [PATCH 31/94] Debugging --- src/interdomain/service/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interdomain/service/__main__.py b/src/interdomain/service/__main__.py index 577af4908..b89e27c61 100644 --- a/src/interdomain/service/__main__.py +++ b/src/interdomain/service/__main__.py @@ -50,7 +50,7 @@ def main(): signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) - LOGGER.info('Starting...') + LOGGER.info('Starting Interdomain Service...') # Start metrics server metrics_port = get_metrics_port() -- GitLab From 31145f270e2d71e6c6fdd46c0f997b14e64d340e Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 25 Jul 2024 11:34:02 +0200 Subject: [PATCH 32/94] Debugging --- src/interdomain/service/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interdomain/service/__main__.py b/src/interdomain/service/__main__.py index b89e27c61..8c392821e 100644 --- a/src/interdomain/service/__main__.py +++ b/src/interdomain/service/__main__.py @@ -71,7 +71,8 @@ def main(): # topology_abstractor.start() # Subscribe to Context Events - dlt_enabled = is_dlt_enabled() + #dlt_enabled = is_dlt_enabled() + dlt_enabled = True if dlt_enabled: LOGGER.info('Starting DLT functionality...') dlt_recorder = DLTRecorder() -- GitLab From 9088c6571c04a645a26097f4b8c78b66a84e3df6 Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 25 Jul 2024 12:02:56 +0200 Subject: [PATCH 33/94] Debugging --- .../topology_abstractor/DltRecorder.py | 29 +++- .../topology_abstractor/DltRecorderOld.py | 133 ++++++++++++++++++ 2 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 src/interdomain/service/topology_abstractor/DltRecorderOld.py diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index cb8c194eb..abcb382f6 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -30,6 +30,7 @@ class DLTRecorder(threading.Thread): self.terminate = threading.Event() self.context_client = ContextClient() self.context_event_collector = EventsCollector(self.context_client) + self.topology_cache = {} def stop(self): self.terminate.set() @@ -105,6 +106,8 @@ class DLTRecorder(threading.Thread): topology_details = self.context_client.GetTopologyDetails(topology_id) topology_name = topology_details.name + self.topology_cache[topology_uuid] = topology_details + if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): for device in topology_details.devices: @@ -118,16 +121,34 @@ class DLTRecorder(threading.Thread): args = context_uuid, context_name, topology_uuid, topology_name, grpc_message_to_json_string(event) LOGGER.warning(MSG.format(*args)) + def find_topology_for_device(self, device_id: DeviceId) -> Optional[TopologyId]: + for topology_id, details in self.topology_cache.items(): + for device in details.devices: + if device.device_id == device_id: + return topology_id + return None -#Check which ID to use. + def find_topology_for_link(self, link_id: LinkId) -> Optional[TopologyId]: + for topology_id, details in self.topology_cache.items(): + for link in details.links: + if link.link_id == link_id: + return topology_id + return None def process_device_event(self, event: DeviceEvent, dlt_record_sender: DltRecordSender) -> None: device_id = event.device_id - device_uuid = device_id.device_uuid.uuid device = self.context_client.GetDevice(device_id) - dlt_record_sender.add_device(device_id.context_id, device) + topology_id = self.find_topology_for_device(device_id) + if topology_id: + dlt_record_sender.add_device(topology_id, device) + else: + LOGGER.warning(f"Topology not found for device {device_id.device_uuid.uuid}") def process_link_event(self, event: LinkEvent, dlt_record_sender: DltRecordSender) -> None: link_id = event.link_id link = self.context_client.GetLink(link_id) - dlt_record_sender.add_link(link_id.context_id, link) + topology_id = self.find_topology_for_link(link_id) + if topology_id: + dlt_record_sender.add_link(topology_id, link) + else: + LOGGER.warning(f"Topology not found for link {link_id.link_uuid.uuid}") diff --git a/src/interdomain/service/topology_abstractor/DltRecorderOld.py b/src/interdomain/service/topology_abstractor/DltRecorderOld.py new file mode 100644 index 000000000..cb8c194eb --- /dev/null +++ b/src/interdomain/service/topology_abstractor/DltRecorderOld.py @@ -0,0 +1,133 @@ +import logging +import threading +from typing import Dict, Optional + +from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum +from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name +from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkEvent, TopologyId, TopologyEvent +from common.tools.context_queries.Context import create_context +from common.tools.context_queries.Device import get_uuids_of_devices_in_topology +from common.tools.context_queries.Topology import create_missing_topologies +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Context import json_context_id +from common.tools.object_factory.Device import json_device_id +from common.tools.object_factory.Topology import json_topology_id +from context.client.ContextClient import ContextClient +from context.client.EventsCollector import EventsCollector +from dlt.connector.client.DltConnectorClient import DltConnectorClient +from .DltRecordSender import DltRecordSender +from .Types import EventTypes + +LOGGER = logging.getLogger(__name__) + +ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) +INTERDOMAIN_TOPOLOGY_ID = TopologyId(**json_topology_id(INTERDOMAIN_TOPOLOGY_NAME, context_id=ADMIN_CONTEXT_ID)) + + +class DLTRecorder(threading.Thread): + def __init__(self) -> None: + super().__init__(daemon=True) + self.terminate = threading.Event() + self.context_client = ContextClient() + self.context_event_collector = EventsCollector(self.context_client) + + def stop(self): + self.terminate.set() + + def run(self) -> None: + self.context_client.connect() + create_context(self.context_client, DEFAULT_CONTEXT_NAME) + self.create_topologies() + self.context_event_collector.start() + + while not self.terminate.is_set(): + event = self.context_event_collector.get_event(timeout=0.1) + if event is None: + continue + LOGGER.info('Processing Event({:s})...'.format(grpc_message_to_json_string(event))) + self.update_record(event) + + self.context_event_collector.stop() + self.context_client.close() + + def create_topologies(self): + topology_uuids = [DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME] + create_missing_topologies(self.context_client, ADMIN_CONTEXT_ID, topology_uuids) + + def get_dlt_connector_client(self) -> Optional[DltConnectorClient]: + env_vars = find_environment_variables([ + get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_HOST), + get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + ]) + if len(env_vars) == 2: + dlt_connector_client = DltConnectorClient() + dlt_connector_client.connect() + return dlt_connector_client + return None + + def update_record(self, event: EventTypes) -> None: + dlt_connector_client = self.get_dlt_connector_client() + dlt_record_sender = DltRecordSender(self.context_client, dlt_connector_client) + + if isinstance(event, ContextEvent): + LOGGER.debug('Processing ContextEvent({:s})'.format(grpc_message_to_json_string(event))) + LOGGER.warning('Ignoring ContextEvent({:s})'.format(grpc_message_to_json_string(event))) + + elif isinstance(event, TopologyEvent): + LOGGER.debug('Processing TopologyEvent({:s})'.format(grpc_message_to_json_string(event))) + self.process_topology_event(event, dlt_record_sender) + + elif isinstance(event, DeviceEvent): + LOGGER.debug('Processing DeviceEvent({:s})'.format(grpc_message_to_json_string(event))) + self.process_device_event(event, dlt_record_sender) + + elif isinstance(event, LinkEvent): + LOGGER.debug('Processing LinkEvent({:s})'.format(grpc_message_to_json_string(event))) + self.process_link_event(event, dlt_record_sender) + + else: + LOGGER.warning('Unsupported Event({:s})'.format(grpc_message_to_json_string(event))) + + dlt_record_sender.commit() + if dlt_connector_client is not None: + dlt_connector_client.close() + + def process_topology_event(self, event: TopologyEvent, dlt_record_sender: DltRecordSender) -> None: + topology_id = event.topology_id + topology_uuid = topology_id.topology_uuid.uuid + context_id = topology_id.context_id + context_uuid = context_id.context_uuid.uuid + topology_uuids = {DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME} + + context = self.context_client.GetContext(context_id) + context_name = context.name + + topology_details = self.context_client.GetTopologyDetails(topology_id) + topology_name = topology_details.name + + if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ + (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): + for device in topology_details.devices: + dlt_record_sender.add_device(topology_id, device) + + for link in topology_details.links: + dlt_record_sender.add_link(topology_id, link) + + else: + MSG = 'Ignoring ({:s}/{:s})({:s}/{:s}) TopologyEvent({:s})' + args = context_uuid, context_name, topology_uuid, topology_name, grpc_message_to_json_string(event) + LOGGER.warning(MSG.format(*args)) + + +#Check which ID to use. + + def process_device_event(self, event: DeviceEvent, dlt_record_sender: DltRecordSender) -> None: + device_id = event.device_id + device_uuid = device_id.device_uuid.uuid + device = self.context_client.GetDevice(device_id) + dlt_record_sender.add_device(device_id.context_id, device) + + def process_link_event(self, event: LinkEvent, dlt_record_sender: DltRecordSender) -> None: + link_id = event.link_id + link = self.context_client.GetLink(link_id) + dlt_record_sender.add_link(link_id.context_id, link) -- GitLab From c103dda5732b111229fbef7f40f016d541a67363 Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 25 Jul 2024 12:07:15 +0200 Subject: [PATCH 34/94] Debugging --- src/interdomain/service/topology_abstractor/DltRecorder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index abcb382f6..1c5661b60 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -4,7 +4,7 @@ from typing import Dict, Optional from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name -from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkEvent, TopologyId, TopologyEvent +from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkId, LinkEvent, TopologyId, TopologyEvent from common.tools.context_queries.Context import create_context from common.tools.context_queries.Device import get_uuids_of_devices_in_topology from common.tools.context_queries.Topology import create_missing_topologies -- GitLab From 99c55e1b2ea1dbdadc4c6dfc991d80a470540c6f Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 25 Jul 2024 12:18:31 +0200 Subject: [PATCH 35/94] Debugging --- .../topology_abstractor/DltRecorder.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 1c5661b60..6f4ce5e59 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -4,7 +4,7 @@ from typing import Dict, Optional from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name -from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkId, LinkEvent, TopologyId, TopologyEvent +from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkEvent, TopologyId, TopologyEvent from common.tools.context_queries.Context import create_context from common.tools.context_queries.Device import get_uuids_of_devices_in_topology from common.tools.context_queries.Topology import create_missing_topologies @@ -30,7 +30,7 @@ class DLTRecorder(threading.Thread): self.terminate = threading.Event() self.context_client = ContextClient() self.context_event_collector = EventsCollector(self.context_client) - self.topology_cache = {} + self.topology_cache: Dict[str, TopologyId] = {} def stop(self): self.terminate.set() @@ -56,15 +56,19 @@ class DLTRecorder(threading.Thread): create_missing_topologies(self.context_client, ADMIN_CONTEXT_ID, topology_uuids) def get_dlt_connector_client(self) -> Optional[DltConnectorClient]: - env_vars = find_environment_variables([ - get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_HOST), - get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), - ]) - if len(env_vars) == 2: - dlt_connector_client = DltConnectorClient() - dlt_connector_client.connect() - return dlt_connector_client - return None + # Always enable DLT for testing + dlt_connector_client = DltConnectorClient() + dlt_connector_client.connect() + return dlt_connector_client + # env_vars = find_environment_variables([ + # get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_HOST), + # get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + # ]) + # if len(env_vars) == 2: + # dlt_connector_client = DltConnectorClient() + # dlt_connector_client.connect() + # return dlt_connector_client + # return None def update_record(self, event: EventTypes) -> None: dlt_connector_client = self.get_dlt_connector_client() @@ -106,7 +110,7 @@ class DLTRecorder(threading.Thread): topology_details = self.context_client.GetTopologyDetails(topology_id) topology_name = topology_details.name - self.topology_cache[topology_uuid] = topology_details + self.topology_cache[topology_uuid] = topology_id if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): @@ -122,14 +126,16 @@ class DLTRecorder(threading.Thread): LOGGER.warning(MSG.format(*args)) def find_topology_for_device(self, device_id: DeviceId) -> Optional[TopologyId]: - for topology_id, details in self.topology_cache.items(): + for topology_uuid, topology_id in self.topology_cache.items(): + details = self.context_client.GetTopologyDetails(topology_id) for device in details.devices: if device.device_id == device_id: return topology_id return None def find_topology_for_link(self, link_id: LinkId) -> Optional[TopologyId]: - for topology_id, details in self.topology_cache.items(): + for topology_uuid, topology_id in self.topology_cache.items(): + details = self.context_client.GetTopologyDetails(topology_id) for link in details.links: if link.link_id == link_id: return topology_id -- GitLab From d3baf581bd091ec349861afca7c2277602453936 Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 25 Jul 2024 12:19:08 +0200 Subject: [PATCH 36/94] Debugging --- src/interdomain/service/topology_abstractor/DltRecorder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 6f4ce5e59..ae1702a59 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -4,7 +4,7 @@ from typing import Dict, Optional from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name -from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkEvent, TopologyId, TopologyEvent +from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkId, LinkEvent, TopologyId, TopologyEvent from common.tools.context_queries.Context import create_context from common.tools.context_queries.Device import get_uuids_of_devices_in_topology from common.tools.context_queries.Topology import create_missing_topologies -- GitLab From 73e0528c30ead568905df66b9589330d6d2fbfb6 Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 25 Jul 2024 16:12:48 +0200 Subject: [PATCH 37/94] Debugging --- src/dlt/connector/client/DltConnectorClient.py | 1 + src/dlt/connector/service/DltConnectorServiceServicerImpl.py | 1 + src/interdomain/service/topology_abstractor/DltRecorder.py | 1 + 3 files changed, 3 insertions(+) diff --git a/src/dlt/connector/client/DltConnectorClient.py b/src/dlt/connector/client/DltConnectorClient.py index c3101d65e..e383217d8 100644 --- a/src/dlt/connector/client/DltConnectorClient.py +++ b/src/dlt/connector/client/DltConnectorClient.py @@ -22,6 +22,7 @@ from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.grpc.Tools import grpc_message_to_json_string LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) MAX_RETRIES = 15 DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') diff --git a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py index 42e86b102..c05d46b48 100644 --- a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py +++ b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py @@ -25,6 +25,7 @@ from dlt.connector.client.DltGatewayClient import DltGatewayClient from .tools.Checkers import record_exists LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) METRICS_POOL = MetricsPool('DltConnector', 'RPC') diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index ae1702a59..b4d18e9bc 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -19,6 +19,7 @@ from .DltRecordSender import DltRecordSender from .Types import EventTypes LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) INTERDOMAIN_TOPOLOGY_ID = TopologyId(**json_topology_id(INTERDOMAIN_TOPOLOGY_NAME, context_id=ADMIN_CONTEXT_ID)) -- GitLab From 9ee1f5ca288a6364d40d69c02f723469dd660a8e Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 25 Jul 2024 16:31:06 +0200 Subject: [PATCH 38/94] Returns empty value if there is no record found by a key. --- src/dlt/gateway/dltApp/src/dltGateway.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dlt/gateway/dltApp/src/dltGateway.js b/src/dlt/gateway/dltApp/src/dltGateway.js index f0ca8120b..7ee723e7c 100644 --- a/src/dlt/gateway/dltApp/src/dltGateway.js +++ b/src/dlt/gateway/dltApp/src/dltGateway.js @@ -101,7 +101,10 @@ async function getFromDlt(call, callback) { // Send the response with the formatted JSON data callback(null, { record_id: call.request, data_json: JSON.stringify(result) }); - } catch (error) { + } catch (error) { if (error.message.includes("data not found for key")) { + // Return an empty response when no record is found + callback(null, { record_id: call.request, data_json: "{}" }); + } else { // Send failure response with error message callback({ code: grpc.status.UNKNOWN, -- GitLab From 8c890d224afffb473ffcf85c45952dbaec82570c Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 25 Jul 2024 16:38:07 +0200 Subject: [PATCH 39/94] Return empty value if there are no records match a key --- src/dlt/gateway/dltApp/src/dltGateway.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dlt/gateway/dltApp/src/dltGateway.js b/src/dlt/gateway/dltApp/src/dltGateway.js index 7ee723e7c..80eeebc17 100644 --- a/src/dlt/gateway/dltApp/src/dltGateway.js +++ b/src/dlt/gateway/dltApp/src/dltGateway.js @@ -101,7 +101,8 @@ async function getFromDlt(call, callback) { // Send the response with the formatted JSON data callback(null, { record_id: call.request, data_json: JSON.stringify(result) }); - } catch (error) { if (error.message.includes("data not found for key")) { + } catch (error) { + if (error.message.includes("data not found for key")) { // Return an empty response when no record is found callback(null, { record_id: call.request, data_json: "{}" }); } else { @@ -110,6 +111,7 @@ async function getFromDlt(call, callback) { code: grpc.status.UNKNOWN, details: error.message }); + } } } -- GitLab From 9609a1ade36a5c3e428074ad4d5bd561a944cd62 Mon Sep 17 00:00:00 2001 From: diazjj Date: Thu, 25 Jul 2024 17:14:35 +0200 Subject: [PATCH 40/94] Return Empty DataJson if record is not found --- src/dlt/gateway/dltApp/src/dltGateway.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dlt/gateway/dltApp/src/dltGateway.js b/src/dlt/gateway/dltApp/src/dltGateway.js index 80eeebc17..c2cfb5e45 100644 --- a/src/dlt/gateway/dltApp/src/dltGateway.js +++ b/src/dlt/gateway/dltApp/src/dltGateway.js @@ -104,7 +104,7 @@ async function getFromDlt(call, callback) { } catch (error) { if (error.message.includes("data not found for key")) { // Return an empty response when no record is found - callback(null, { record_id: call.request, data_json: "{}" }); + callback(null, { record_id: call.request, data_json: "" }); } else { // Send failure response with error message callback({ -- GitLab From 19cc0f7cdf327a826055f852d4417f54a3007bad Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 26 Jul 2024 09:36:35 +0200 Subject: [PATCH 41/94] Debugging --- src/interdomain/service/topology_abstractor/DltRecorder.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index b4d18e9bc..53d2dbe31 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -113,6 +113,8 @@ class DLTRecorder(threading.Thread): self.topology_cache[topology_uuid] = topology_id + LOGGER.debug('TOPOLOGY Details({:s})'.format(grpc_message_to_json_string(topology_details))) + if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): for device in topology_details.devices: -- GitLab From 250172128fe5a76463cd3936b28e68905c43a75c Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 26 Jul 2024 12:20:04 +0200 Subject: [PATCH 42/94] Updated the way the dlt_gateway handles chaincode responses --- src/dlt/gateway/dltApp/src/dltGateway.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dlt/gateway/dltApp/src/dltGateway.js b/src/dlt/gateway/dltApp/src/dltGateway.js index c2cfb5e45..f4f9c0d0a 100644 --- a/src/dlt/gateway/dltApp/src/dltGateway.js +++ b/src/dlt/gateway/dltApp/src/dltGateway.js @@ -100,7 +100,7 @@ async function getFromDlt(call, callback) { const result = JSON.parse(resultJson); // Send the response with the formatted JSON data - callback(null, { record_id: call.request, data_json: JSON.stringify(result) }); + callback(null, { record_id: call.request, operation: result.operation, data_json: result.data_json }); } catch (error) { if (error.message.includes("data not found for key")) { // Return an empty response when no record is found -- GitLab From 65ffb21328aaa60dcceb81dc2e6957c3cf59cf20 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 26 Jul 2024 13:17:22 +0200 Subject: [PATCH 43/94] Debugging --- src/dlt/connector/client/DltGatewayClient.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dlt/connector/client/DltGatewayClient.py b/src/dlt/connector/client/DltGatewayClient.py index 31ad4cca2..cde278517 100644 --- a/src/dlt/connector/client/DltGatewayClient.py +++ b/src/dlt/connector/client/DltGatewayClient.py @@ -23,6 +23,7 @@ from common.tools.grpc.Tools import grpc_message_to_json_string from dlt.connector.Config import DLT_GATEWAY_HOST, DLT_GATEWAY_PORT LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) MAX_RETRIES = 15 DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') -- GitLab From 3973667383b02166ab59f7c32ae79b989fbdcffd Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 26 Jul 2024 13:26:21 +0200 Subject: [PATCH 44/94] Debugging --- src/dlt/gateway/dltApp/src/dltGateway.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dlt/gateway/dltApp/src/dltGateway.js b/src/dlt/gateway/dltApp/src/dltGateway.js index f4f9c0d0a..f39a6cb32 100644 --- a/src/dlt/gateway/dltApp/src/dltGateway.js +++ b/src/dlt/gateway/dltApp/src/dltGateway.js @@ -74,7 +74,7 @@ async function recordToDlt(call, callback) { callback(null, { record_id, status: 'DLTRECORDSTATUS_SUCCEEDED' }); } catch (error) { // Send failure response with error message - //console.log("ERRROR", error) + console.log("ERRROR", error) callback(null, { record_id, status: 'DLTRECORDSTATUS_FAILED', error_message: error.message }); } } -- GitLab From 30468bc7ffe5a64d0edc397120089e4df4fb3907 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 26 Jul 2024 14:41:08 +0200 Subject: [PATCH 45/94] Debugging --- src/dlt/gateway/dltApp/src/dltGateway.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dlt/gateway/dltApp/src/dltGateway.js b/src/dlt/gateway/dltApp/src/dltGateway.js index f39a6cb32..0dcfb7508 100644 --- a/src/dlt/gateway/dltApp/src/dltGateway.js +++ b/src/dlt/gateway/dltApp/src/dltGateway.js @@ -104,6 +104,7 @@ async function getFromDlt(call, callback) { } catch (error) { if (error.message.includes("data not found for key")) { // Return an empty response when no record is found + console.log("REQUEST ERROR:", error); callback(null, { record_id: call.request, data_json: "" }); } else { // Send failure response with error message -- GitLab From fe74613c866585fc23627578ab2a1abcd3b1c0a6 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 26 Jul 2024 14:57:48 +0200 Subject: [PATCH 46/94] Debugging --- src/dlt/connector/service/tools/Checkers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dlt/connector/service/tools/Checkers.py b/src/dlt/connector/service/tools/Checkers.py index 6ad0f4b82..f628126ec 100644 --- a/src/dlt/connector/service/tools/Checkers.py +++ b/src/dlt/connector/service/tools/Checkers.py @@ -19,6 +19,6 @@ def record_exists(record : DltRecord) -> bool: exists = exists and (len(record.record_id.domain_uuid.uuid) > 0) exists = exists and (record.record_id.type != DLTRECORDTYPE_UNDEFINED) exists = exists and (len(record.record_id.record_uuid.uuid) > 0) - #exists = exists and (record.operation != DLTRECORDOPERATION_UNDEFINED) + exists = exists and (record.operation != DLTRECORDOPERATION_UNDEFINED) exists = exists and (len(record.data_json) > 0) return exists -- GitLab From 4371a0830b91f4110c76966f0de8a284c32ce57e Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 26 Jul 2024 15:04:50 +0200 Subject: [PATCH 47/94] Debugging --- src/dlt/connector/service/tools/Checkers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dlt/connector/service/tools/Checkers.py b/src/dlt/connector/service/tools/Checkers.py index f628126ec..94a10d409 100644 --- a/src/dlt/connector/service/tools/Checkers.py +++ b/src/dlt/connector/service/tools/Checkers.py @@ -19,6 +19,6 @@ def record_exists(record : DltRecord) -> bool: exists = exists and (len(record.record_id.domain_uuid.uuid) > 0) exists = exists and (record.record_id.type != DLTRECORDTYPE_UNDEFINED) exists = exists and (len(record.record_id.record_uuid.uuid) > 0) - exists = exists and (record.operation != DLTRECORDOPERATION_UNDEFINED) - exists = exists and (len(record.data_json) > 0) + #exists = exists and (record.operation != DLTRECORDOPERATION_UNDEFINED) + #exists = exists and (len(record.data_json) > 0) return exists -- GitLab From 22ffe9d0ad143a85cfa24b9f9e919bf281d0affd Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 26 Jul 2024 15:15:50 +0200 Subject: [PATCH 48/94] Debugging --- src/dlt/connector/service/tools/Checkers.py | 2 +- src/dlt/gateway/dltApp/src/dltGateway.js | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/dlt/connector/service/tools/Checkers.py b/src/dlt/connector/service/tools/Checkers.py index 94a10d409..6ad0f4b82 100644 --- a/src/dlt/connector/service/tools/Checkers.py +++ b/src/dlt/connector/service/tools/Checkers.py @@ -20,5 +20,5 @@ def record_exists(record : DltRecord) -> bool: exists = exists and (record.record_id.type != DLTRECORDTYPE_UNDEFINED) exists = exists and (len(record.record_id.record_uuid.uuid) > 0) #exists = exists and (record.operation != DLTRECORDOPERATION_UNDEFINED) - #exists = exists and (len(record.data_json) > 0) + exists = exists and (len(record.data_json) > 0) return exists diff --git a/src/dlt/gateway/dltApp/src/dltGateway.js b/src/dlt/gateway/dltApp/src/dltGateway.js index 0dcfb7508..ba9a67a31 100644 --- a/src/dlt/gateway/dltApp/src/dltGateway.js +++ b/src/dlt/gateway/dltApp/src/dltGateway.js @@ -93,7 +93,7 @@ async function getFromDlt(call, callback) { try { console.log("RECEIVED CALL REQUEST:", call.request); - const { record_uuid } = call.request; + //const { record_id, operation, data_json } = call.request; const resultBytes = await contractInstance.evaluateTransaction('RetrieveRecord', JSON.stringify(call.request)); // Decode and parse the result const resultJson = utf8Decoder.decode(resultBytes); @@ -105,7 +105,12 @@ async function getFromDlt(call, callback) { if (error.message.includes("data not found for key")) { // Return an empty response when no record is found console.log("REQUEST ERROR:", error); - callback(null, { record_id: call.request, data_json: "" }); + const emptyRecordId = { + domain_uuid: { uuid: "" }, + type: 'DLTRECORDTYPE_UNDEFINED', + record_uuid: { uuid: "" } + }; + callback(null, { record_id: emptyRecordId, data_json: "" }); } else { // Send failure response with error message callback({ -- GitLab From 21a3d49ce3b68e411a81004ea74ddad5ded8d605 Mon Sep 17 00:00:00 2001 From: diazjj Date: Fri, 26 Jul 2024 15:22:09 +0200 Subject: [PATCH 49/94] Debugging --- src/dlt/connector/service/tools/Checkers.py | 2 +- .../topology_abstractor/DltRecorderOld.py | 133 ------------------ 2 files changed, 1 insertion(+), 134 deletions(-) delete mode 100644 src/interdomain/service/topology_abstractor/DltRecorderOld.py diff --git a/src/dlt/connector/service/tools/Checkers.py b/src/dlt/connector/service/tools/Checkers.py index 6ad0f4b82..94a10d409 100644 --- a/src/dlt/connector/service/tools/Checkers.py +++ b/src/dlt/connector/service/tools/Checkers.py @@ -20,5 +20,5 @@ def record_exists(record : DltRecord) -> bool: exists = exists and (record.record_id.type != DLTRECORDTYPE_UNDEFINED) exists = exists and (len(record.record_id.record_uuid.uuid) > 0) #exists = exists and (record.operation != DLTRECORDOPERATION_UNDEFINED) - exists = exists and (len(record.data_json) > 0) + #exists = exists and (len(record.data_json) > 0) return exists diff --git a/src/interdomain/service/topology_abstractor/DltRecorderOld.py b/src/interdomain/service/topology_abstractor/DltRecorderOld.py deleted file mode 100644 index cb8c194eb..000000000 --- a/src/interdomain/service/topology_abstractor/DltRecorderOld.py +++ /dev/null @@ -1,133 +0,0 @@ -import logging -import threading -from typing import Dict, Optional - -from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum -from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name -from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkEvent, TopologyId, TopologyEvent -from common.tools.context_queries.Context import create_context -from common.tools.context_queries.Device import get_uuids_of_devices_in_topology -from common.tools.context_queries.Topology import create_missing_topologies -from common.tools.grpc.Tools import grpc_message_to_json_string -from common.tools.object_factory.Context import json_context_id -from common.tools.object_factory.Device import json_device_id -from common.tools.object_factory.Topology import json_topology_id -from context.client.ContextClient import ContextClient -from context.client.EventsCollector import EventsCollector -from dlt.connector.client.DltConnectorClient import DltConnectorClient -from .DltRecordSender import DltRecordSender -from .Types import EventTypes - -LOGGER = logging.getLogger(__name__) - -ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) -INTERDOMAIN_TOPOLOGY_ID = TopologyId(**json_topology_id(INTERDOMAIN_TOPOLOGY_NAME, context_id=ADMIN_CONTEXT_ID)) - - -class DLTRecorder(threading.Thread): - def __init__(self) -> None: - super().__init__(daemon=True) - self.terminate = threading.Event() - self.context_client = ContextClient() - self.context_event_collector = EventsCollector(self.context_client) - - def stop(self): - self.terminate.set() - - def run(self) -> None: - self.context_client.connect() - create_context(self.context_client, DEFAULT_CONTEXT_NAME) - self.create_topologies() - self.context_event_collector.start() - - while not self.terminate.is_set(): - event = self.context_event_collector.get_event(timeout=0.1) - if event is None: - continue - LOGGER.info('Processing Event({:s})...'.format(grpc_message_to_json_string(event))) - self.update_record(event) - - self.context_event_collector.stop() - self.context_client.close() - - def create_topologies(self): - topology_uuids = [DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME] - create_missing_topologies(self.context_client, ADMIN_CONTEXT_ID, topology_uuids) - - def get_dlt_connector_client(self) -> Optional[DltConnectorClient]: - env_vars = find_environment_variables([ - get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_HOST), - get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), - ]) - if len(env_vars) == 2: - dlt_connector_client = DltConnectorClient() - dlt_connector_client.connect() - return dlt_connector_client - return None - - def update_record(self, event: EventTypes) -> None: - dlt_connector_client = self.get_dlt_connector_client() - dlt_record_sender = DltRecordSender(self.context_client, dlt_connector_client) - - if isinstance(event, ContextEvent): - LOGGER.debug('Processing ContextEvent({:s})'.format(grpc_message_to_json_string(event))) - LOGGER.warning('Ignoring ContextEvent({:s})'.format(grpc_message_to_json_string(event))) - - elif isinstance(event, TopologyEvent): - LOGGER.debug('Processing TopologyEvent({:s})'.format(grpc_message_to_json_string(event))) - self.process_topology_event(event, dlt_record_sender) - - elif isinstance(event, DeviceEvent): - LOGGER.debug('Processing DeviceEvent({:s})'.format(grpc_message_to_json_string(event))) - self.process_device_event(event, dlt_record_sender) - - elif isinstance(event, LinkEvent): - LOGGER.debug('Processing LinkEvent({:s})'.format(grpc_message_to_json_string(event))) - self.process_link_event(event, dlt_record_sender) - - else: - LOGGER.warning('Unsupported Event({:s})'.format(grpc_message_to_json_string(event))) - - dlt_record_sender.commit() - if dlt_connector_client is not None: - dlt_connector_client.close() - - def process_topology_event(self, event: TopologyEvent, dlt_record_sender: DltRecordSender) -> None: - topology_id = event.topology_id - topology_uuid = topology_id.topology_uuid.uuid - context_id = topology_id.context_id - context_uuid = context_id.context_uuid.uuid - topology_uuids = {DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME} - - context = self.context_client.GetContext(context_id) - context_name = context.name - - topology_details = self.context_client.GetTopologyDetails(topology_id) - topology_name = topology_details.name - - if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ - (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): - for device in topology_details.devices: - dlt_record_sender.add_device(topology_id, device) - - for link in topology_details.links: - dlt_record_sender.add_link(topology_id, link) - - else: - MSG = 'Ignoring ({:s}/{:s})({:s}/{:s}) TopologyEvent({:s})' - args = context_uuid, context_name, topology_uuid, topology_name, grpc_message_to_json_string(event) - LOGGER.warning(MSG.format(*args)) - - -#Check which ID to use. - - def process_device_event(self, event: DeviceEvent, dlt_record_sender: DltRecordSender) -> None: - device_id = event.device_id - device_uuid = device_id.device_uuid.uuid - device = self.context_client.GetDevice(device_id) - dlt_record_sender.add_device(device_id.context_id, device) - - def process_link_event(self, event: LinkEvent, dlt_record_sender: DltRecordSender) -> None: - link_id = event.link_id - link = self.context_client.GetLink(link_id) - dlt_record_sender.add_link(link_id.context_id, link) -- GitLab From 439212b7aa408638206ae475c5c91c91cfba3a07 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 09:45:01 +0200 Subject: [PATCH 50/94] Debugging --- src/dlt/connector/service/__main__.py | 2 ++ .../service/topology_abstractor/DltRecordSender.py | 4 +++- src/interdomain/service/topology_abstractor/DltRecorder.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/dlt/connector/service/__main__.py b/src/dlt/connector/service/__main__.py index 5e0fb6f87..b13f4257b 100644 --- a/src/dlt/connector/service/__main__.py +++ b/src/dlt/connector/service/__main__.py @@ -53,6 +53,8 @@ def main(): event_dispatcher = DltEventDispatcher() event_dispatcher.start() + # Context Event dispatcher + # Starting DLT connector service grpc_service = DltConnectorService() grpc_service.start() diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index a504fe01b..fd5bfa35a 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -21,6 +21,7 @@ from dlt.connector.client.DltConnectorClient import DltConnectorClient from .Types import DltRecordTypes LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) class DltRecordSender: def __init__(self, context_client : ContextClient, dlt_connector_client : Optional[DltConnectorClient]) -> None: @@ -64,7 +65,8 @@ class DltRecordSender: for dlt_record_uuid in self.dlt_record_uuids: topology_id,dlt_record = self.dlt_record_uuid_to_data[dlt_record_uuid] if isinstance(dlt_record, Device): - device_id = self.context_client.SetDevice(dlt_record) + device_id = self.context_client.SetDevice(dlt_record) # Se retriggerea el Evento. + LOGGER.debug('DEVICE ID: ({:s})'.format(str(dlt_record))) if self.dlt_connector_client is None: continue dlt_device_id = DltDeviceId() dlt_device_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 53d2dbe31..7c48c750a 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -118,6 +118,7 @@ class DLTRecorder(threading.Thread): if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): for device in topology_details.devices: + LOGGER.debug('DEVICE INFO({:s})'.format(grpc_message_to_json_string(device))) dlt_record_sender.add_device(topology_id, device) for link in topology_details.links: @@ -149,6 +150,7 @@ class DLTRecorder(threading.Thread): device = self.context_client.GetDevice(device_id) topology_id = self.find_topology_for_device(device_id) if topology_id: + LOGGER.debug('DEVICE INFO({:s}), DEVICE ID ({:s})'.format(grpc_message_to_json_string(device)), (str(device_id))) dlt_record_sender.add_device(topology_id, device) else: LOGGER.warning(f"Topology not found for device {device_id.device_uuid.uuid}") -- GitLab From 0944f56ec1f0bd470461821d885e42ceb2ea1514 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 09:55:49 +0200 Subject: [PATCH 51/94] debugging --- src/interdomain/service/topology_abstractor/DltRecordSender.py | 2 +- src/interdomain/service/topology_abstractor/DltRecorder.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index fd5bfa35a..12722925a 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -66,7 +66,7 @@ class DltRecordSender: topology_id,dlt_record = self.dlt_record_uuid_to_data[dlt_record_uuid] if isinstance(dlt_record, Device): device_id = self.context_client.SetDevice(dlt_record) # Se retriggerea el Evento. - LOGGER.debug('DEVICE ID: ({:s})'.format(str(dlt_record))) + LOGGER.debug('DEVICE_ID: ({:s})'.format(str(dlt_record))) if self.dlt_connector_client is None: continue dlt_device_id = DltDeviceId() dlt_device_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 7c48c750a..3588fa836 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -150,7 +150,8 @@ class DLTRecorder(threading.Thread): device = self.context_client.GetDevice(device_id) topology_id = self.find_topology_for_device(device_id) if topology_id: - LOGGER.debug('DEVICE INFO({:s}), DEVICE ID ({:s})'.format(grpc_message_to_json_string(device)), (str(device_id))) + LOGGER.debug('DEVICE_INFO({:s}), DEVICE_ID ({:s})'.format(grpc_message_to_json_string(device), str(device_id))) + dlt_record_sender.add_device(topology_id, device) else: LOGGER.warning(f"Topology not found for device {device_id.device_uuid.uuid}") -- GitLab From e803ec1cdd099a11fd30c98c1a14980532a03963 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 10:07:34 +0200 Subject: [PATCH 52/94] debugging --- src/interdomain/service/topology_abstractor/DltRecordSender.py | 2 +- src/interdomain/service/topology_abstractor/DltRecorder.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index 12722925a..7710b9473 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -66,7 +66,7 @@ class DltRecordSender: topology_id,dlt_record = self.dlt_record_uuid_to_data[dlt_record_uuid] if isinstance(dlt_record, Device): device_id = self.context_client.SetDevice(dlt_record) # Se retriggerea el Evento. - LOGGER.debug('DEVICE_ID: ({:s})'.format(str(dlt_record))) + LOGGER.debug('DEVICE_ID: ({:s})'.format(str(device_id))) if self.dlt_connector_client is None: continue dlt_device_id = DltDeviceId() dlt_device_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 3588fa836..2adac1726 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -150,7 +150,7 @@ class DLTRecorder(threading.Thread): device = self.context_client.GetDevice(device_id) topology_id = self.find_topology_for_device(device_id) if topology_id: - LOGGER.debug('DEVICE_INFO({:s}), DEVICE_ID ({:s})'.format(grpc_message_to_json_string(device), str(device_id))) + LOGGER.debug('DEVICE_INFO({:s}), DEVICE_ID ({:s})'.format(grpc_message_to_json_string(device), grpc_message_to_json_string(device_id))) dlt_record_sender.add_device(topology_id, device) else: -- GitLab From f1f7ce2fdbeb6ac1812a1b816e6406d90bc07aba Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 10:31:02 +0200 Subject: [PATCH 53/94] debugging --- src/interdomain/service/topology_abstractor/DltRecordSender.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index 7710b9473..eb3503580 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -16,6 +16,7 @@ import logging from typing import Dict, List, Optional, Tuple from common.proto.context_pb2 import Device, Link, Service, Slice, TopologyId from common.proto.dlt_connector_pb2 import DltDeviceId, DltLinkId, DltServiceId, DltSliceId +from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient from dlt.connector.client.DltConnectorClient import DltConnectorClient from .Types import DltRecordTypes @@ -66,7 +67,7 @@ class DltRecordSender: topology_id,dlt_record = self.dlt_record_uuid_to_data[dlt_record_uuid] if isinstance(dlt_record, Device): device_id = self.context_client.SetDevice(dlt_record) # Se retriggerea el Evento. - LOGGER.debug('DEVICE_ID: ({:s})'.format(str(device_id))) + LOGGER.debug('DEVICE_ID: ({:s})'.format(grpc_message_to_json_string(device_id))) if self.dlt_connector_client is None: continue dlt_device_id = DltDeviceId() dlt_device_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member -- GitLab From bd90b6ed483f594f419bbfdfc493ebfc717f0c93 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 10:45:41 +0200 Subject: [PATCH 54/94] debugging --- .../service/topology_abstractor/DltRecordSender.py | 4 ++-- src/interdomain/service/topology_abstractor/DltRecorder.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index eb3503580..26adee518 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -66,8 +66,8 @@ class DltRecordSender: for dlt_record_uuid in self.dlt_record_uuids: topology_id,dlt_record = self.dlt_record_uuid_to_data[dlt_record_uuid] if isinstance(dlt_record, Device): - device_id = self.context_client.SetDevice(dlt_record) # Se retriggerea el Evento. - LOGGER.debug('DEVICE_ID: ({:s})'.format(grpc_message_to_json_string(device_id))) + #device_id = self.context_client.SetDevice(dlt_record) # Se retriggerea el Evento. + #LOGGER.debug('DEVICE_ID: ({:s})'.format(grpc_message_to_json_string(device_id))) if self.dlt_connector_client is None: continue dlt_device_id = DltDeviceId() dlt_device_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 2adac1726..209be30d1 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -118,7 +118,7 @@ class DLTRecorder(threading.Thread): if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): for device in topology_details.devices: - LOGGER.debug('DEVICE INFO({:s})'.format(grpc_message_to_json_string(device))) + #LOGGER.debug('DEVICE INFO({:s})'.format(grpc_message_to_json_string(device))) dlt_record_sender.add_device(topology_id, device) for link in topology_details.links: @@ -150,7 +150,7 @@ class DLTRecorder(threading.Thread): device = self.context_client.GetDevice(device_id) topology_id = self.find_topology_for_device(device_id) if topology_id: - LOGGER.debug('DEVICE_INFO({:s}), DEVICE_ID ({:s})'.format(grpc_message_to_json_string(device), grpc_message_to_json_string(device_id))) + LOGGER.debug('DEVICE_INFO({:s}), DEVICE_ID ({:s})'.format(str(device.device_id.device_uuid.uuid), grpc_message_to_json_string(device_id))) dlt_record_sender.add_device(topology_id, device) else: -- GitLab From edd4b3c2c671abbde66e29a5986cd276f6f97f99 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 10:46:53 +0200 Subject: [PATCH 55/94] debugging --- src/interdomain/service/topology_abstractor/DltRecordSender.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index 26adee518..b49af3310 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -66,7 +66,7 @@ class DltRecordSender: for dlt_record_uuid in self.dlt_record_uuids: topology_id,dlt_record = self.dlt_record_uuid_to_data[dlt_record_uuid] if isinstance(dlt_record, Device): - #device_id = self.context_client.SetDevice(dlt_record) # Se retriggerea el Evento. + device_id = self.context_client.SetDevice(dlt_record) # Se retriggerea el Evento. #LOGGER.debug('DEVICE_ID: ({:s})'.format(grpc_message_to_json_string(device_id))) if self.dlt_connector_client is None: continue dlt_device_id = DltDeviceId() -- GitLab From 185aecaea1322062ae602f32018e2b2be2b02e03 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 10:55:06 +0200 Subject: [PATCH 56/94] Debugging --- .../service/topology_abstractor/DltRecordSender.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index b49af3310..0a2019a78 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -66,7 +66,8 @@ class DltRecordSender: for dlt_record_uuid in self.dlt_record_uuids: topology_id,dlt_record = self.dlt_record_uuid_to_data[dlt_record_uuid] if isinstance(dlt_record, Device): - device_id = self.context_client.SetDevice(dlt_record) # Se retriggerea el Evento. + #device_id = self.context_client.SetDevice(dlt_record) # This causes events to be triggered infinitely. + device_id = dlt_record.device_id.device_uuid.uuid #LOGGER.debug('DEVICE_ID: ({:s})'.format(grpc_message_to_json_string(device_id))) if self.dlt_connector_client is None: continue dlt_device_id = DltDeviceId() @@ -74,21 +75,24 @@ class DltRecordSender: dlt_device_id.device_id.CopyFrom(device_id) # pylint: disable=no-member self.dlt_connector_client.RecordDevice(dlt_device_id) elif isinstance(dlt_record, Link): - link_id = self.context_client.SetLink(dlt_record) + #link_id = self.context_client.SetLink(dlt_record) + link_id = dlt_record.link_id.link_uuid.uuid if self.dlt_connector_client is None: continue dlt_link_id = DltLinkId() dlt_link_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_link_id.link_id.CopyFrom(link_id) # pylint: disable=no-member self.dlt_connector_client.RecordLink(dlt_link_id) elif isinstance(dlt_record, Service): - service_id = self.context_client.SetService(dlt_record) + #service_id = self.context_client.SetService(dlt_record) + service_id = dlt_record.service_id.service_uuid.uuid if self.dlt_connector_client is None: continue dlt_service_id = DltServiceId() dlt_service_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_service_id.service_id.CopyFrom(service_id) # pylint: disable=no-member self.dlt_connector_client.RecordService(dlt_service_id) elif isinstance(dlt_record, Slice): - slice_id = self.context_client.SetSlice(dlt_record) + #slice_id = self.context_client.SetSlice(dlt_record) + slice_id = dlt_record.slice_id.slice_uuid.uuid if self.dlt_connector_client is None: continue dlt_slice_id = DltSliceId() dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member -- GitLab From 587983c2a15aff2e927eb9713e0a8d1fad46eafe Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 11:03:17 +0200 Subject: [PATCH 57/94] debugging --- .../service/topology_abstractor/DltRecordSender.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index 0a2019a78..cfa51c928 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -67,7 +67,7 @@ class DltRecordSender: topology_id,dlt_record = self.dlt_record_uuid_to_data[dlt_record_uuid] if isinstance(dlt_record, Device): #device_id = self.context_client.SetDevice(dlt_record) # This causes events to be triggered infinitely. - device_id = dlt_record.device_id.device_uuid.uuid + device_id = dlt_record.device_id #LOGGER.debug('DEVICE_ID: ({:s})'.format(grpc_message_to_json_string(device_id))) if self.dlt_connector_client is None: continue dlt_device_id = DltDeviceId() @@ -76,7 +76,7 @@ class DltRecordSender: self.dlt_connector_client.RecordDevice(dlt_device_id) elif isinstance(dlt_record, Link): #link_id = self.context_client.SetLink(dlt_record) - link_id = dlt_record.link_id.link_uuid.uuid + link_id = dlt_record.link_id if self.dlt_connector_client is None: continue dlt_link_id = DltLinkId() dlt_link_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member @@ -84,7 +84,7 @@ class DltRecordSender: self.dlt_connector_client.RecordLink(dlt_link_id) elif isinstance(dlt_record, Service): #service_id = self.context_client.SetService(dlt_record) - service_id = dlt_record.service_id.service_uuid.uuid + service_id = dlt_record.service_id if self.dlt_connector_client is None: continue dlt_service_id = DltServiceId() dlt_service_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member @@ -92,7 +92,7 @@ class DltRecordSender: self.dlt_connector_client.RecordService(dlt_service_id) elif isinstance(dlt_record, Slice): #slice_id = self.context_client.SetSlice(dlt_record) - slice_id = dlt_record.slice_id.slice_uuid.uuid + slice_id = dlt_record.slice_id if self.dlt_connector_client is None: continue dlt_slice_id = DltSliceId() dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member -- GitLab From 59707d7347f02b040a6763031aa3aee854a4eb03 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 11:30:19 +0200 Subject: [PATCH 58/94] debugging --- src/interdomain/service/topology_abstractor/DltRecorder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 209be30d1..700562b3a 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -118,7 +118,7 @@ class DLTRecorder(threading.Thread): if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): for device in topology_details.devices: - #LOGGER.debug('DEVICE INFO({:s})'.format(grpc_message_to_json_string(device))) + LOGGER.debug('DEVICE INFO({:s})'.format(grpc_message_to_json_string(device))) dlt_record_sender.add_device(topology_id, device) for link in topology_details.links: -- GitLab From 6a73ac1f68d1381d0910bfc5fad6fcc2fc2079e2 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 11:56:16 +0200 Subject: [PATCH 59/94] Debugging --- src/interdomain/service/topology_abstractor/DltRecorder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 700562b3a..0e94159c4 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -118,7 +118,7 @@ class DLTRecorder(threading.Thread): if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): for device in topology_details.devices: - LOGGER.debug('DEVICE INFO({:s})'.format(grpc_message_to_json_string(device))) + LOGGER.debug('DEVICE_INFO_TOPO({:s})'.format(grpc_message_to_json_string(device))) dlt_record_sender.add_device(topology_id, device) for link in topology_details.links: -- GitLab From 8503a835ba6a889cdb47a8666a3abb60fd63242a Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 13:55:08 +0200 Subject: [PATCH 60/94] Debugging --- .../connector/client/DltConnectorClient.py | 36 +++++++++---------- src/dlt/connector/client/DltGatewayClient.py | 20 +++++------ .../DltConnectorServiceServicerImpl.py | 28 +++++++-------- .../topology_abstractor/DltRecordSender.py | 10 +++--- 4 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/dlt/connector/client/DltConnectorClient.py b/src/dlt/connector/client/DltConnectorClient.py index e383217d8..e71cf0bd7 100644 --- a/src/dlt/connector/client/DltConnectorClient.py +++ b/src/dlt/connector/client/DltConnectorClient.py @@ -48,64 +48,64 @@ class DltConnectorClient: self.stub = None @RETRY_DECORATOR - def RecordAll(self, request : TopologyId) -> Empty: + async def RecordAll(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAll request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAll(request) + response = await self.stub.RecordAll(request) LOGGER.debug('RecordAll result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordAllDevices(self, request : TopologyId) -> Empty: + async def RecordAllDevices(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAllDevices request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAllDevices(request) + response = await self.stub.RecordAllDevices(request) LOGGER.debug('RecordAllDevices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordDevice(self, request : DltDeviceId) -> Empty: + async def RecordDevice(self, request : DltDeviceId) -> Empty: LOGGER.debug('RecordDevice request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordDevice(request) + response = await self.stub.RecordDevice(request) LOGGER.debug('RecordDevice result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordAllLinks(self, request : TopologyId) -> Empty: + async def RecordAllLinks(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAllLinks request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAllLinks(request) + response = await self.stub.RecordAllLinks(request) LOGGER.debug('RecordAllLinks result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordLink(self, request : DltLinkId) -> Empty: + async def RecordLink(self, request : DltLinkId) -> Empty: LOGGER.debug('RecordLink request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordLink(request) + response = await self.stub.RecordLink(request) LOGGER.debug('RecordLink result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordAllServices(self, request : TopologyId) -> Empty: + async def RecordAllServices(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAllServices request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAllServices(request) + response = await self.stub.RecordAllServices(request) LOGGER.debug('RecordAllServices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordService(self, request : DltServiceId) -> Empty: + async def RecordService(self, request : DltServiceId) -> Empty: LOGGER.debug('RecordService request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordService(request) + response = await self.stub.RecordService(request) LOGGER.debug('RecordService result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordAllSlices(self, request : TopologyId) -> Empty: + async def RecordAllSlices(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAllSlices request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAllSlices(request) + response = await self.stub.RecordAllSlices(request) LOGGER.debug('RecordAllSlices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordSlice(self, request : DltSliceId) -> Empty: + async def RecordSlice(self, request : DltSliceId) -> Empty: LOGGER.debug('RecordSlice request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordSlice(request) + response = await self.stub.RecordSlice(request) LOGGER.debug('RecordSlice result: {:s}'.format(grpc_message_to_json_string(response))) return response diff --git a/src/dlt/connector/client/DltGatewayClient.py b/src/dlt/connector/client/DltGatewayClient.py index cde278517..b04e08d7f 100644 --- a/src/dlt/connector/client/DltGatewayClient.py +++ b/src/dlt/connector/client/DltGatewayClient.py @@ -49,36 +49,36 @@ class DltGatewayClient: self.stub = None @RETRY_DECORATOR - def RecordToDlt(self, request : DltRecord) -> DltRecordStatus: + async def RecordToDlt(self, request : DltRecord) -> DltRecordStatus: LOGGER.debug('RecordToDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordToDlt(request) + response = await self.stub.RecordToDlt(request) LOGGER.debug('RecordToDlt result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def GetFromDlt(self, request : DltRecordId) -> DltRecord: + async def GetFromDlt(self, request : DltRecordId) -> DltRecord: LOGGER.debug('GetFromDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.GetFromDlt(request) + response = await self.stub.GetFromDlt(request) LOGGER.debug('GetFromDlt result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def SubscribeToDlt(self, request : DltRecordSubscription) -> Iterator[DltRecordEvent]: + async def SubscribeToDlt(self, request : DltRecordSubscription) -> Iterator[DltRecordEvent]: LOGGER.debug('SubscribeToDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.SubscribeToDlt(request) + response = await self.stub.SubscribeToDlt(request) LOGGER.debug('SubscribeToDlt result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def GetDltStatus(self, request : TeraFlowController) -> DltPeerStatus: + async def GetDltStatus(self, request : TeraFlowController) -> DltPeerStatus: LOGGER.debug('GetDltStatus request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.GetDltStatus(request) + response = await self.stub.GetDltStatus(request) LOGGER.debug('GetDltStatus result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def GetDltPeers(self, request : Empty) -> DltPeerStatusList: + async def GetDltPeers(self, request : Empty) -> DltPeerStatusList: LOGGER.debug('GetDltPeers request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.GetDltPeers(request) + response = await self.stub.GetDltPeers(request) LOGGER.debug('GetDltPeers result: {:s}'.format(grpc_message_to_json_string(response))) return response diff --git a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py index c05d46b48..df5e8fd08 100644 --- a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py +++ b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py @@ -35,22 +35,22 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): LOGGER.debug('Servicer Created') @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordAll(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + async def RecordAll(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordAllDevices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + async def RecordAllDevices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordDevice(self, request : DltDeviceId, context : grpc.ServicerContext) -> Empty: + async def RecordDevice(self, request : DltDeviceId, context : grpc.ServicerContext) -> Empty: data_json = None if not request.delete: context_client = ContextClient() device = context_client.GetDevice(request.device_id) data_json = grpc_message_to_json_string(device) - self._record_entity( + await self._record_entity( request.topology_id.topology_uuid.uuid, DltRecordTypeEnum.DLTRECORDTYPE_DEVICE, request.device_id.device_uuid.uuid, request.delete, data_json) return Empty() @@ -60,53 +60,53 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordLink(self, request : DltLinkId, context : grpc.ServicerContext) -> Empty: + async def RecordLink(self, request : DltLinkId, context : grpc.ServicerContext) -> Empty: data_json = None if not request.delete: context_client = ContextClient() link = context_client.GetLink(request.link_id) data_json = grpc_message_to_json_string(link) - self._record_entity( + await self._record_entity( request.topology_id.topology_uuid.uuid, DltRecordTypeEnum.DLTRECORDTYPE_LINK, request.link_id.link_uuid.uuid, request.delete, data_json) return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordAllServices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + async def RecordAllServices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordService(self, request : DltServiceId, context : grpc.ServicerContext) -> Empty: + async def RecordService(self, request : DltServiceId, context : grpc.ServicerContext) -> Empty: data_json = None if not request.delete: context_client = ContextClient() service = context_client.GetService(request.service_id) data_json = grpc_message_to_json_string(service) - self._record_entity( + await self._record_entity( request.topology_id.topology_uuid.uuid, DltRecordTypeEnum.DLTRECORDTYPE_SERVICE, request.service_id.service_uuid.uuid, request.delete, data_json) return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordAllSlices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + async def RecordAllSlices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordSlice(self, request : DltSliceId, context : grpc.ServicerContext) -> Empty: + async def RecordSlice(self, request : DltSliceId, context : grpc.ServicerContext) -> Empty: data_json = None if not request.delete: context_client = ContextClient() slice_ = context_client.GetSlice(request.slice_id) data_json = grpc_message_to_json_string(slice_) - self._record_entity( + await self._record_entity( request.topology_id.topology_uuid.uuid, DltRecordTypeEnum.DLTRECORDTYPE_SLICE, request.slice_id.slice_uuid.uuid, request.delete, data_json) return Empty() - def _record_entity( + async def _record_entity( self, dlt_domain_uuid : str, dlt_record_type : DltRecordTypeEnum, dlt_record_uuid : str, delete : bool, data_json : Optional[str] = None ) -> None: @@ -143,6 +143,6 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): str_dlt_record = grpc_message_to_json_string(dlt_record) LOGGER.debug('[_record_entity] sent dlt_record = {:s}'.format(str_dlt_record)) - dlt_record_status = dltgateway_client.RecordToDlt(dlt_record) + dlt_record_status = await dltgateway_client.RecordToDlt(dlt_record) str_dlt_record_status = grpc_message_to_json_string(dlt_record_status) LOGGER.debug('[_record_entity] recv dlt_record_status = {:s}'.format(str_dlt_record_status)) diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index cfa51c928..c17878e0e 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -62,7 +62,7 @@ class DltRecordSender: record_uuid = '{:s}:slice:{:s}/{:s}'.format(topology_uuid, context_uuid, slice_uuid) self._add_record(record_uuid, (topology_id, slice_)) - def commit(self) -> None: + async def commit(self) -> None: for dlt_record_uuid in self.dlt_record_uuids: topology_id,dlt_record = self.dlt_record_uuid_to_data[dlt_record_uuid] if isinstance(dlt_record, Device): @@ -73,7 +73,7 @@ class DltRecordSender: dlt_device_id = DltDeviceId() dlt_device_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_device_id.device_id.CopyFrom(device_id) # pylint: disable=no-member - self.dlt_connector_client.RecordDevice(dlt_device_id) + await self.dlt_connector_client.RecordDevice(dlt_device_id) elif isinstance(dlt_record, Link): #link_id = self.context_client.SetLink(dlt_record) link_id = dlt_record.link_id @@ -81,7 +81,7 @@ class DltRecordSender: dlt_link_id = DltLinkId() dlt_link_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_link_id.link_id.CopyFrom(link_id) # pylint: disable=no-member - self.dlt_connector_client.RecordLink(dlt_link_id) + await self.dlt_connector_client.RecordLink(dlt_link_id) elif isinstance(dlt_record, Service): #service_id = self.context_client.SetService(dlt_record) service_id = dlt_record.service_id @@ -89,7 +89,7 @@ class DltRecordSender: dlt_service_id = DltServiceId() dlt_service_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_service_id.service_id.CopyFrom(service_id) # pylint: disable=no-member - self.dlt_connector_client.RecordService(dlt_service_id) + await self.dlt_connector_client.RecordService(dlt_service_id) elif isinstance(dlt_record, Slice): #slice_id = self.context_client.SetSlice(dlt_record) slice_id = dlt_record.slice_id @@ -97,6 +97,6 @@ class DltRecordSender: dlt_slice_id = DltSliceId() dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member - self.dlt_connector_client.RecordSlice(dlt_slice_id) + await self.dlt_connector_client.RecordSlice(dlt_slice_id) else: LOGGER.error('Unsupported Record({:s})'.format(str(dlt_record))) -- GitLab From 77f239659bfb284464ea6675f349b389fad77c67 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 14:10:41 +0200 Subject: [PATCH 61/94] Debugging --- .../connector/client/DltConnectorClient.py | 2 +- .../client/DltEventsCollector copy.py | 95 +++++++++++++++++++ .../connector/client/DltEventsCollector.py | 82 ++++++++-------- src/dlt/connector/client/DltGatewayClient.py | 2 +- src/dlt/connector/client/async.py | 74 +++++++++++++++ 5 files changed, 215 insertions(+), 40 deletions(-) create mode 100644 src/dlt/connector/client/DltEventsCollector copy.py create mode 100644 src/dlt/connector/client/async.py diff --git a/src/dlt/connector/client/DltConnectorClient.py b/src/dlt/connector/client/DltConnectorClient.py index e71cf0bd7..7cfb6b594 100644 --- a/src/dlt/connector/client/DltConnectorClient.py +++ b/src/dlt/connector/client/DltConnectorClient.py @@ -39,7 +39,7 @@ class DltConnectorClient: LOGGER.debug('Channel created') def connect(self): - self.channel = grpc.insecure_channel(self.endpoint) + self.channel = grpc.aio.insecure_channel(self.endpoint) self.stub = DltConnectorServiceStub(self.channel) def close(self): diff --git a/src/dlt/connector/client/DltEventsCollector copy.py b/src/dlt/connector/client/DltEventsCollector copy.py new file mode 100644 index 000000000..9fac60b7c --- /dev/null +++ b/src/dlt/connector/client/DltEventsCollector copy.py @@ -0,0 +1,95 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Callable, Optional +import asyncio +import grpc, logging, queue, threading, time +from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordSubscription +from common.tools.grpc.Tools import grpc_message_to_json_string +from dlt.connector.client.DltGatewayClient import DltGatewayClient + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +# This class accepts an event_handler method as attribute that can be used to pre-process and +# filter events before they reach the events_queue. Depending on the handler, the supported +# behaviors are: +# - If the handler is not set, the events are transparently added to the events_queue. +# - If returns None for an event, the event is not stored in the events_queue. +# - If returns a DltRecordEvent object for an event, the returned event is stored in the events_queue. +# - Other combinations are not supported. + +class DltEventsCollector(threading.Thread): + def __init__( + self, dltgateway_client : DltGatewayClient, + log_events_received : bool = False, + event_handler : Optional[Callable[[DltRecordEvent], Optional[DltRecordEvent]]] = None, + ) -> None: + super().__init__(name='DltEventsCollector', daemon=True) + self._dltgateway_client = dltgateway_client + self._log_events_received = log_events_received + self._event_handler = event_handler + self._events_queue = queue.Queue() + self._terminate = threading.Event() + self._dltgateway_stream = None + + def run(self) -> None: + event_handler = self._event_handler + if event_handler is None: event_handler = lambda e: e + self._terminate.clear() + while not self._terminate.is_set(): + try: + subscription = DltRecordSubscription() # bu default subscribe to all + self._dltgateway_stream = self._dltgateway_client.SubscribeToDlt(subscription) + for event in self._dltgateway_stream: + if self._log_events_received: + LOGGER.info('[_collect] event: {:s}'.format(grpc_message_to_json_string(event))) + event = event_handler(event) + if event is None: continue + if not isinstance(event, DltRecordEvent): + # pylint: disable=broad-exception-raised + raise Exception('Unsupported return type: {:s}'.format(str(event))) + self._events_queue.put_nowait(event) + except grpc.RpcError as e: + if e.code() == grpc.StatusCode.UNAVAILABLE: # pylint: disable=no-member + time.sleep(0.5) + continue + elif e.code() == grpc.StatusCode.CANCELLED: # pylint: disable=no-member + break + else: + raise # pragma: no cover + + def get_event(self, block : bool = True, timeout : float = 0.1): + try: + return self._events_queue.get(block=block, timeout=timeout) + except queue.Empty: # pylint: disable=catching-non-exception + return None + + def get_events(self, block : bool = True, timeout : float = 0.1, count : int = None): + events = [] + if count is None: + while True: + event = self.get_event(block=block, timeout=timeout) + if event is None: break + events.append(event) + else: + for _ in range(count): + event = self.get_event(block=block, timeout=timeout) + if event is None: continue + events.append(event) + return sorted(events, key=lambda e: e.event.timestamp.timestamp) + + def stop(self): + self._terminate.set() + if self._dltgateway_stream is not None: self._dltgateway_stream.cancel() diff --git a/src/dlt/connector/client/DltEventsCollector.py b/src/dlt/connector/client/DltEventsCollector.py index e59784a4d..39cf993f3 100644 --- a/src/dlt/connector/client/DltEventsCollector.py +++ b/src/dlt/connector/client/DltEventsCollector.py @@ -13,7 +13,9 @@ # limitations under the License. from typing import Callable, Optional -import grpc, logging, queue, threading, time +import asyncio +import grpc +import logging from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordSubscription from common.tools.grpc.Tools import grpc_message_to_json_string from dlt.connector.client.DltGatewayClient import DltGatewayClient @@ -21,74 +23,78 @@ from dlt.connector.client.DltGatewayClient import DltGatewayClient LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) -# This class accepts an event_handler method as attribute that can be used to pre-process and -# filter events before they reach the events_queue. Depending on the handler, the supported -# behaviors are: -# - If the handler is not set, the events are transparently added to the events_queue. -# - If returns None for an event, the event is not stored in the events_queue. -# - If returns a DltRecordEvent object for an event, the returned event is stored in the events_queue. -# - Other combinations are not supported. - -class DltEventsCollector(threading.Thread): +class DltEventsCollector: def __init__( - self, dltgateway_client : DltGatewayClient, - log_events_received : bool = False, - event_handler : Optional[Callable[[DltRecordEvent], Optional[DltRecordEvent]]] = None, + self, dltgateway_client: DltGatewayClient, + log_events_received: bool = False, + event_handler: Optional[Callable[[DltRecordEvent], Optional[DltRecordEvent]]] = None, ) -> None: - super().__init__(name='DltEventsCollector', daemon=True) self._dltgateway_client = dltgateway_client self._log_events_received = log_events_received self._event_handler = event_handler - self._events_queue = queue.Queue() - self._terminate = threading.Event() + self._events_queue = asyncio.Queue() + self._terminate = asyncio.Event() self._dltgateway_stream = None - def run(self) -> None: + async def start(self) -> None: event_handler = self._event_handler - if event_handler is None: event_handler = lambda e: e + if event_handler is None: + event_handler = lambda e: e self._terminate.clear() while not self._terminate.is_set(): try: - subscription = DltRecordSubscription() # bu default subscribe to all - self._dltgateway_stream = self._dltgateway_client.SubscribeToDlt(subscription) - for event in self._dltgateway_stream: + subscription = DltRecordSubscription() # by default subscribe to all + self._dltgateway_stream = await self._dltgateway_client.SubscribeToDlt(subscription) + async for event in self._dltgateway_stream: if self._log_events_received: LOGGER.info('[_collect] event: {:s}'.format(grpc_message_to_json_string(event))) event = event_handler(event) - if event is None: continue + if event is None: + continue if not isinstance(event, DltRecordEvent): - # pylint: disable=broad-exception-raised raise Exception('Unsupported return type: {:s}'.format(str(event))) - self._events_queue.put_nowait(event) + await self._events_queue.put(event) except grpc.RpcError as e: - if e.code() == grpc.StatusCode.UNAVAILABLE: # pylint: disable=no-member - time.sleep(0.5) + if e.code() == grpc.StatusCode.UNAVAILABLE: # pylint: disable=no-member + await asyncio.sleep(0.5) continue - elif e.code() == grpc.StatusCode.CANCELLED: # pylint: disable=no-member + elif e.code() == grpc.StatusCode.CANCELLED: # pylint: disable=no-member break else: - raise # pragma: no cover + raise # pragma: no cover - def get_event(self, block : bool = True, timeout : float = 0.1): + async def get_event(self, block: bool = True, timeout: float = 0.1): try: - return self._events_queue.get(block=block, timeout=timeout) - except queue.Empty: # pylint: disable=catching-non-exception + return await asyncio.wait_for(self._events_queue.get(), timeout) + except asyncio.TimeoutError: return None - def get_events(self, block : bool = True, timeout : float = 0.1, count : int = None): + async def get_events(self, block: bool = True, timeout: float = 0.1, count: int = None): events = [] if count is None: while True: - event = self.get_event(block=block, timeout=timeout) - if event is None: break + event = await self.get_event(block=block, timeout=timeout) + if event is None: + break events.append(event) else: for _ in range(count): - event = self.get_event(block=block, timeout=timeout) - if event is None: continue + event = await self.get_event(block=block, timeout=timeout) + if event is None: + continue events.append(event) return sorted(events, key=lambda e: e.event.timestamp.timestamp) - def stop(self): + async def stop(self): self._terminate.set() - if self._dltgateway_stream is not None: self._dltgateway_stream.cancel() + if self._dltgateway_stream is not None: + await self._dltgateway_stream.cancel() + +# Usage example +async def main(): + gateway_client = DltGatewayClient() + collector = DltEventsCollector(gateway_client) + await collector.start() + +# Start the event loop +asyncio.run(main()) diff --git a/src/dlt/connector/client/DltGatewayClient.py b/src/dlt/connector/client/DltGatewayClient.py index b04e08d7f..f9b37a2db 100644 --- a/src/dlt/connector/client/DltGatewayClient.py +++ b/src/dlt/connector/client/DltGatewayClient.py @@ -40,7 +40,7 @@ class DltGatewayClient: LOGGER.debug('Channel created') def connect(self): - self.channel = grpc.insecure_channel(self.endpoint) + self.channel = grpc.aio.insecure_channel(self.endpoint) self.stub = DltGatewayServiceStub(self.channel) def close(self): diff --git a/src/dlt/connector/client/async.py b/src/dlt/connector/client/async.py new file mode 100644 index 000000000..e38f124c2 --- /dev/null +++ b/src/dlt/connector/client/async.py @@ -0,0 +1,74 @@ +# DltGatewayClient.py + +import grpc +import logging +from typing import Iterator +from common.proto.context_pb2 import Empty, TeraFlowController +from common.proto.dlt_gateway_pb2 import ( + DltPeerStatus, DltPeerStatusList, DltRecord, DltRecordEvent, DltRecordId, DltRecordStatus, DltRecordSubscription +) +from common.proto.dlt_gateway_pb2_grpc import DltGatewayServiceStub +from common.tools.client.RetryDecorator import retry, delay_exponential +from common.tools.grpc.Tools import grpc_message_to_json_string +from dlt.connector.Config import DLT_GATEWAY_HOST, DLT_GATEWAY_PORT + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +MAX_RETRIES = 15 +DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) +RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') + +class DltGatewayClient: + def __init__(self, host=None, port=None): + if not host: host = DLT_GATEWAY_HOST + if not port: port = DLT_GATEWAY_PORT + self.endpoint = '{:s}:{:s}'.format(str(host), str(port)) + LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) + self.channel = None + self.stub = None + self.connect() + LOGGER.debug('Channel created') + + def connect(self): + self.channel = grpc.aio.insecure_channel(self.endpoint) + self.stub = DltGatewayServiceStub(self.channel) + + def close(self): + if self.channel is not None: self.channel.close() + self.channel = None + self.stub = None + + @RETRY_DECORATOR + async def RecordToDlt(self, request: DltRecord) -> DltRecordStatus: + LOGGER.debug('RecordToDlt request: {:s}'.format(grpc_message_to_json_string(request))) + response = await self.stub.RecordToDlt(request) + LOGGER.debug('RecordToDlt result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + async def GetFromDlt(self, request: DltRecordId) -> DltRecord: + LOGGER.debug('GetFromDlt request: {:s}'.format(grpc_message_to_json_string(request))) + response = await self.stub.GetFromDlt(request) + LOGGER.debug('GetFromDlt result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + async def SubscribeToDlt(self, request: DltRecordSubscription) -> Iterator[DltRecordEvent]: + LOGGER.debug('SubscribeToDlt request: {:s}'.format(grpc_message_to_json_string(request))) + response = await self.stub.SubscribeToDlt(request) + LOGGER.debug('SubscribeToDlt result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + async def GetDltStatus(self, request: TeraFlowController) -> DltPeerStatus: + LOGGER.debug('GetDltStatus request: {:s}'.format(grpc_message_to_json_string(request))) + response = await self.stub.GetDltStatus(request) + LOGGER.debug('GetDltStatus result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + async def GetDltPeers(self, request: Empty) -> DltPeerStatusList: + LOGGER.debug('GetDltPeers request: {:s}'.format(grpc_message_to_json_string(request))) + response = await self.stub.GetDltPeers(request) + LOGGER.debug('GetDltPeers result: {:s}'.format(grpc_message_to_json_string(response))) + return response -- GitLab From c41f38d9ad608ee1252b9fe0e538d2e617c034e8 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 14:16:14 +0200 Subject: [PATCH 62/94] debugging --- src/dlt/connector/client/DltEventsCollector.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/dlt/connector/client/DltEventsCollector.py b/src/dlt/connector/client/DltEventsCollector.py index 39cf993f3..31eaf8542 100644 --- a/src/dlt/connector/client/DltEventsCollector.py +++ b/src/dlt/connector/client/DltEventsCollector.py @@ -36,7 +36,7 @@ class DltEventsCollector: self._terminate = asyncio.Event() self._dltgateway_stream = None - async def start(self) -> None: + async def run(self) -> None: event_handler = self._event_handler if event_handler is None: event_handler = lambda e: e @@ -90,11 +90,3 @@ class DltEventsCollector: if self._dltgateway_stream is not None: await self._dltgateway_stream.cancel() -# Usage example -async def main(): - gateway_client = DltGatewayClient() - collector = DltEventsCollector(gateway_client) - await collector.start() - -# Start the event loop -asyncio.run(main()) -- GitLab From ebb001dbafbc5cf740490d321c3ba16ddd4bd97f Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 14:20:58 +0200 Subject: [PATCH 63/94] debugging --- .../DltEventDispatcher copy.py | 209 ++++++++++++++++++ .../event_dispatcher/DltEventDispatcher.py | 86 +++---- 2 files changed, 245 insertions(+), 50 deletions(-) create mode 100644 src/dlt/connector/service/event_dispatcher/DltEventDispatcher copy.py diff --git a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher copy.py b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher copy.py new file mode 100644 index 000000000..779bae9c1 --- /dev/null +++ b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher copy.py @@ -0,0 +1,209 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import grpc, json, logging, threading +from typing import Any, Dict, Set +from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME +from common.proto.context_pb2 import ContextId, Device, EventTypeEnum, Link, Slice, TopologyId +from common.proto.dlt_connector_pb2 import DltSliceId +from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordOperationEnum, DltRecordTypeEnum +from common.tools.context_queries.Context import create_context +from common.tools.context_queries.Device import add_device_to_topology +from common.tools.context_queries.Link import add_link_to_topology +from common.tools.context_queries.Topology import create_topology +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Context import json_context_id +from common.tools.object_factory.Topology import json_topology_id +from context.client.ContextClient import ContextClient +from dlt.connector.client.DltConnectorClient import DltConnectorClient +from dlt.connector.client.DltEventsCollector import DltEventsCollector +from dlt.connector.client.DltGatewayClient import DltGatewayClient +from interdomain.client.InterdomainClient import InterdomainClient + +LOGGER = logging.getLogger(__name__) + +GET_EVENT_TIMEOUT = 0.5 + +ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) + +class Clients: + def __init__(self) -> None: + self.context_client = ContextClient() + self.dlt_connector_client = DltConnectorClient() + self.dlt_gateway_client = DltGatewayClient() + self.interdomain_client = InterdomainClient() + + def close(self) -> None: + self.interdomain_client.close() + self.dlt_gateway_client.close() + self.dlt_connector_client.close() + self.context_client.close() + +class DltEventDispatcher(threading.Thread): + def __init__(self) -> None: + LOGGER.debug('Creating connector...') + super().__init__(name='DltEventDispatcher', daemon=True) + self._terminate = threading.Event() + LOGGER.debug('Connector created') + + def start(self) -> None: + self._terminate.clear() + return super().start() + + def stop(self): + self._terminate.set() + + def run(self) -> None: + clients = Clients() + create_context(clients.context_client, DEFAULT_CONTEXT_NAME) + create_topology(clients.context_client, DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME) + create_topology(clients.context_client, DEFAULT_CONTEXT_NAME, INTERDOMAIN_TOPOLOGY_NAME) + + dlt_events_collector = DltEventsCollector(clients.dlt_gateway_client, log_events_received=True) + dlt_events_collector.start() + + while not self._terminate.is_set(): + event = dlt_events_collector.get_event(block=True, timeout=GET_EVENT_TIMEOUT) + if event is None: continue + + existing_topology_ids = clients.context_client.ListTopologyIds(ADMIN_CONTEXT_ID) + local_domain_uuids = { + topology_id.topology_uuid.uuid for topology_id in existing_topology_ids.topology_ids + } + local_domain_uuids.discard(DEFAULT_TOPOLOGY_NAME) + local_domain_uuids.discard(INTERDOMAIN_TOPOLOGY_NAME) + + self.dispatch_event(clients, local_domain_uuids, event) + + dlt_events_collector.stop() + clients.close() + + def dispatch_event(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: + record_type : DltRecordTypeEnum = event.record_id.type # {UNDEFINED/CONTEXT/TOPOLOGY/DEVICE/LINK/SERVICE/SLICE} + if record_type == DltRecordTypeEnum.DLTRECORDTYPE_DEVICE: + self._dispatch_device(clients, local_domain_uuids, event) + elif record_type == DltRecordTypeEnum.DLTRECORDTYPE_LINK: + self._dispatch_link(clients, local_domain_uuids, event) + elif record_type == DltRecordTypeEnum.DLTRECORDTYPE_SLICE: + self._dispatch_slice(clients, local_domain_uuids, event) + else: + raise NotImplementedError('EventType: {:s}'.format(grpc_message_to_json_string(event))) + + def _dispatch_device(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: + domain_uuid : str = event.record_id.domain_uuid.uuid + + if domain_uuid in local_domain_uuids: + MSG = '[_dispatch_device] Ignoring DLT event received (local): {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(event))) + return + + MSG = '[_dispatch_device] DLT event received (remote): {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(event))) + + event_type : EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} + if event_type in {EventTypeEnum.EVENTTYPE_CREATE, EventTypeEnum.EVENTTYPE_UPDATE}: + LOGGER.info('[_dispatch_device] event.record_id={:s}'.format(grpc_message_to_json_string(event.record_id))) + record = clients.dlt_gateway_client.GetFromDlt(event.record_id) + LOGGER.info('[_dispatch_device] record={:s}'.format(grpc_message_to_json_string(record))) + + create_context(clients.context_client, domain_uuid) + create_topology(clients.context_client, domain_uuid, DEFAULT_TOPOLOGY_NAME) + device = Device(**json.loads(record.data_json)) + clients.context_client.SetDevice(device) + device_uuid = device.device_id.device_uuid.uuid # pylint: disable=no-member + add_device_to_topology(clients.context_client, ADMIN_CONTEXT_ID, INTERDOMAIN_TOPOLOGY_NAME, device_uuid) + domain_context_id = ContextId(**json_context_id(domain_uuid)) + add_device_to_topology(clients.context_client, domain_context_id, DEFAULT_TOPOLOGY_NAME, device_uuid) + elif event_type in {EventTypeEnum.EVENTTYPE_DELETE}: + raise NotImplementedError('Delete Device') + + def _dispatch_link(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: + domain_uuid : str = event.record_id.domain_uuid.uuid + + if domain_uuid in local_domain_uuids: + MSG = '[_dispatch_link] Ignoring DLT event received (local): {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(event))) + return + + MSG = '[_dispatch_link] DLT event received (remote): {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(event))) + + event_type : EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} + if event_type in {EventTypeEnum.EVENTTYPE_CREATE, EventTypeEnum.EVENTTYPE_UPDATE}: + LOGGER.info('[_dispatch_link] event.record_id={:s}'.format(grpc_message_to_json_string(event.record_id))) + record = clients.dlt_gateway_client.GetFromDlt(event.record_id) + LOGGER.info('[_dispatch_link] record={:s}'.format(grpc_message_to_json_string(record))) + + link = Link(**json.loads(record.data_json)) + clients.context_client.SetLink(link) + link_uuid = link.link_id.link_uuid.uuid # pylint: disable=no-member + add_link_to_topology(clients.context_client, ADMIN_CONTEXT_ID, INTERDOMAIN_TOPOLOGY_NAME, link_uuid) + elif event_type in {EventTypeEnum.EVENTTYPE_DELETE}: + raise NotImplementedError('Delete Link') + + def _dispatch_slice(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: + event_type : EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} + domain_uuid : str = event.record_id.domain_uuid.uuid + + LOGGER.info('[_dispatch_slice] event.record_id={:s}'.format(grpc_message_to_json_string(event.record_id))) + record = clients.dlt_gateway_client.GetFromDlt(event.record_id) + LOGGER.info('[_dispatch_slice] record={:s}'.format(grpc_message_to_json_string(record))) + + slice_ = Slice(**json.loads(record.data_json)) + + context_uuid = slice_.slice_id.context_id.context_uuid.uuid + owner_uuid = slice_.slice_owner.owner_uuid.uuid + create_context(clients.context_client, context_uuid) + create_topology(clients.context_client, context_uuid, DEFAULT_TOPOLOGY_NAME) + + if domain_uuid in local_domain_uuids: + # it is for "me" + if event_type in {EventTypeEnum.EVENTTYPE_CREATE, EventTypeEnum.EVENTTYPE_UPDATE}: + try: + db_slice = clients.context_client.GetSlice(slice_.slice_id) + # exists + db_json_slice = grpc_message_to_json_string(db_slice) + except grpc.RpcError: + # not exists + db_json_slice = None + + _json_slice = grpc_message_to_json_string(slice_) + if db_json_slice != _json_slice: + # not exists or is different... + slice_id = clients.interdomain_client.RequestSlice(slice_) + topology_id = TopologyId(**json_topology_id(domain_uuid)) + dlt_slice_id = DltSliceId() + dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member + dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member + clients.dlt_connector_client.RecordSlice(dlt_slice_id) + + elif event_type in {EventTypeEnum.EVENTTYPE_DELETE}: + raise NotImplementedError('Delete Slice') + elif owner_uuid in local_domain_uuids: + # it is owned by me + # just update it locally + LOGGER.info('[_dispatch_slice] updating locally') + + local_slice = Slice() + local_slice.CopyFrom(slice_) + + # pylint: disable=no-member + del local_slice.slice_service_ids[:] # they are from remote domains so will not be present locally + del local_slice.slice_subslice_ids[:] # they are from remote domains so will not be present locally + + clients.context_client.SetSlice(local_slice) + else: + MSG = '[_dispatch_slice] Ignoring DLT event received (remote): {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(event))) + diff --git a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py index 779bae9c1..76104e2b7 100644 --- a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py +++ b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py @@ -1,23 +1,11 @@ -# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import grpc, json, logging, threading +import asyncio +import logging +import json from typing import Any, Dict, Set from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME from common.proto.context_pb2 import ContextId, Device, EventTypeEnum, Link, Slice, TopologyId from common.proto.dlt_connector_pb2 import DltSliceId -from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordOperationEnum, DltRecordTypeEnum +from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordTypeEnum from common.tools.context_queries.Context import create_context from common.tools.context_queries.Device import add_device_to_topology from common.tools.context_queries.Link import add_link_to_topology @@ -50,31 +38,30 @@ class Clients: self.dlt_connector_client.close() self.context_client.close() -class DltEventDispatcher(threading.Thread): +class DltEventDispatcher: def __init__(self) -> None: LOGGER.debug('Creating connector...') - super().__init__(name='DltEventDispatcher', daemon=True) - self._terminate = threading.Event() + self._terminate = asyncio.Event() LOGGER.debug('Connector created') - def start(self) -> None: + async def start(self) -> None: self._terminate.clear() - return super().start() + await self.run() - def stop(self): + async def stop(self): self._terminate.set() - def run(self) -> None: + async def run(self) -> None: clients = Clients() create_context(clients.context_client, DEFAULT_CONTEXT_NAME) create_topology(clients.context_client, DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME) create_topology(clients.context_client, DEFAULT_CONTEXT_NAME, INTERDOMAIN_TOPOLOGY_NAME) dlt_events_collector = DltEventsCollector(clients.dlt_gateway_client, log_events_received=True) - dlt_events_collector.start() + await dlt_events_collector.run() while not self._terminate.is_set(): - event = dlt_events_collector.get_event(block=True, timeout=GET_EVENT_TIMEOUT) + event = await dlt_events_collector.get_event(block=True, timeout=GET_EVENT_TIMEOUT) if event is None: continue existing_topology_ids = clients.context_client.ListTopologyIds(ADMIN_CONTEXT_ID) @@ -84,24 +71,24 @@ class DltEventDispatcher(threading.Thread): local_domain_uuids.discard(DEFAULT_TOPOLOGY_NAME) local_domain_uuids.discard(INTERDOMAIN_TOPOLOGY_NAME) - self.dispatch_event(clients, local_domain_uuids, event) + await self.dispatch_event(clients, local_domain_uuids, event) - dlt_events_collector.stop() + await dlt_events_collector.stop() clients.close() - def dispatch_event(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: - record_type : DltRecordTypeEnum = event.record_id.type # {UNDEFINED/CONTEXT/TOPOLOGY/DEVICE/LINK/SERVICE/SLICE} + async def dispatch_event(self, clients: Clients, local_domain_uuids: Set[str], event: DltRecordEvent) -> None: + record_type: DltRecordTypeEnum = event.record_id.type # {UNDEFINED/CONTEXT/TOPOLOGY/DEVICE/LINK/SERVICE/SLICE} if record_type == DltRecordTypeEnum.DLTRECORDTYPE_DEVICE: - self._dispatch_device(clients, local_domain_uuids, event) + await self._dispatch_device(clients, local_domain_uuids, event) elif record_type == DltRecordTypeEnum.DLTRECORDTYPE_LINK: - self._dispatch_link(clients, local_domain_uuids, event) + await self._dispatch_link(clients, local_domain_uuids, event) elif record_type == DltRecordTypeEnum.DLTRECORDTYPE_SLICE: - self._dispatch_slice(clients, local_domain_uuids, event) + await self._dispatch_slice(clients, local_domain_uuids, event) else: raise NotImplementedError('EventType: {:s}'.format(grpc_message_to_json_string(event))) - def _dispatch_device(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: - domain_uuid : str = event.record_id.domain_uuid.uuid + async def _dispatch_device(self, clients: Clients, local_domain_uuids: Set[str], event: DltRecordEvent) -> None: + domain_uuid: str = event.record_id.domain_uuid.uuid if domain_uuid in local_domain_uuids: MSG = '[_dispatch_device] Ignoring DLT event received (local): {:s}' @@ -111,25 +98,25 @@ class DltEventDispatcher(threading.Thread): MSG = '[_dispatch_device] DLT event received (remote): {:s}' LOGGER.info(MSG.format(grpc_message_to_json_string(event))) - event_type : EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} + event_type: EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} if event_type in {EventTypeEnum.EVENTTYPE_CREATE, EventTypeEnum.EVENTTYPE_UPDATE}: LOGGER.info('[_dispatch_device] event.record_id={:s}'.format(grpc_message_to_json_string(event.record_id))) - record = clients.dlt_gateway_client.GetFromDlt(event.record_id) + record = await clients.dlt_gateway_client.GetFromDlt(event.record_id) LOGGER.info('[_dispatch_device] record={:s}'.format(grpc_message_to_json_string(record))) create_context(clients.context_client, domain_uuid) create_topology(clients.context_client, domain_uuid, DEFAULT_TOPOLOGY_NAME) device = Device(**json.loads(record.data_json)) clients.context_client.SetDevice(device) - device_uuid = device.device_id.device_uuid.uuid # pylint: disable=no-member + device_uuid = device.device_id.device_uuid.uuid # pylint: disable=no-member add_device_to_topology(clients.context_client, ADMIN_CONTEXT_ID, INTERDOMAIN_TOPOLOGY_NAME, device_uuid) domain_context_id = ContextId(**json_context_id(domain_uuid)) add_device_to_topology(clients.context_client, domain_context_id, DEFAULT_TOPOLOGY_NAME, device_uuid) elif event_type in {EventTypeEnum.EVENTTYPE_DELETE}: raise NotImplementedError('Delete Device') - def _dispatch_link(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: - domain_uuid : str = event.record_id.domain_uuid.uuid + async def _dispatch_link(self, clients: Clients, local_domain_uuids: Set[str], event: DltRecordEvent) -> None: + domain_uuid: str = event.record_id.domain_uuid.uuid if domain_uuid in local_domain_uuids: MSG = '[_dispatch_link] Ignoring DLT event received (local): {:s}' @@ -139,25 +126,25 @@ class DltEventDispatcher(threading.Thread): MSG = '[_dispatch_link] DLT event received (remote): {:s}' LOGGER.info(MSG.format(grpc_message_to_json_string(event))) - event_type : EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} + event_type: EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} if event_type in {EventTypeEnum.EVENTTYPE_CREATE, EventTypeEnum.EVENTTYPE_UPDATE}: LOGGER.info('[_dispatch_link] event.record_id={:s}'.format(grpc_message_to_json_string(event.record_id))) - record = clients.dlt_gateway_client.GetFromDlt(event.record_id) + record = await clients.dlt_gateway_client.GetFromDlt(event.record_id) LOGGER.info('[_dispatch_link] record={:s}'.format(grpc_message_to_json_string(record))) link = Link(**json.loads(record.data_json)) clients.context_client.SetLink(link) - link_uuid = link.link_id.link_uuid.uuid # pylint: disable=no-member + link_uuid = link.link_id.link_uuid.uuid # pylint: disable=no-member add_link_to_topology(clients.context_client, ADMIN_CONTEXT_ID, INTERDOMAIN_TOPOLOGY_NAME, link_uuid) elif event_type in {EventTypeEnum.EVENTTYPE_DELETE}: raise NotImplementedError('Delete Link') - def _dispatch_slice(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: - event_type : EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} - domain_uuid : str = event.record_id.domain_uuid.uuid + async def _dispatch_slice(self, clients: Clients, local_domain_uuids: Set[str], event: DltRecordEvent) -> None: + event_type: EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} + domain_uuid: str = event.record_id.domain_uuid.uuid LOGGER.info('[_dispatch_slice] event.record_id={:s}'.format(grpc_message_to_json_string(event.record_id))) - record = clients.dlt_gateway_client.GetFromDlt(event.record_id) + record = await clients.dlt_gateway_client.GetFromDlt(event.record_id) LOGGER.info('[_dispatch_slice] record={:s}'.format(grpc_message_to_json_string(record))) slice_ = Slice(**json.loads(record.data_json)) @@ -185,7 +172,7 @@ class DltEventDispatcher(threading.Thread): topology_id = TopologyId(**json_topology_id(domain_uuid)) dlt_slice_id = DltSliceId() dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member - dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member + dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member clients.dlt_connector_client.RecordSlice(dlt_slice_id) elif event_type in {EventTypeEnum.EVENTTYPE_DELETE}: @@ -199,11 +186,10 @@ class DltEventDispatcher(threading.Thread): local_slice.CopyFrom(slice_) # pylint: disable=no-member - del local_slice.slice_service_ids[:] # they are from remote domains so will not be present locally - del local_slice.slice_subslice_ids[:] # they are from remote domains so will not be present locally + del local_slice.slice_service_ids[:] # they are from remote domains so will not be present locally + del local_slice.slice_subslice_ids[:] # they are from remote domains so will not be present locally clients.context_client.SetSlice(local_slice) else: MSG = '[_dispatch_slice] Ignoring DLT event received (remote): {:s}' LOGGER.info(MSG.format(grpc_message_to_json_string(event))) - -- GitLab From 2f93875892a5c6c489cabf2267629475fb7c0cf6 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 14:35:52 +0200 Subject: [PATCH 64/94] debugging --- src/interdomain/service/__main__.py | 16 +- .../topology_abstractor/DltRecorder copy.py | 166 ++++++++++++++++++ .../topology_abstractor/DltRecorder.py | 89 +++++----- 3 files changed, 222 insertions(+), 49 deletions(-) create mode 100644 src/interdomain/service/topology_abstractor/DltRecorder copy.py diff --git a/src/interdomain/service/__main__.py b/src/interdomain/service/__main__.py index 8c392821e..3f5ccd183 100644 --- a/src/interdomain/service/__main__.py +++ b/src/interdomain/service/__main__.py @@ -13,6 +13,8 @@ # limitations under the License. import logging, signal, sys, threading +import asyncio + from prometheus_client import start_http_server from common.Constants import ServiceNameEnum from common.Settings import ( @@ -27,6 +29,12 @@ from .RemoteDomainClients import RemoteDomainClients terminate = threading.Event() LOGGER : logging.Logger = None + +async def run_dlt_recorder(dlt_recorder): + await dlt_recorder.start() + await terminate.wait() + await dlt_recorder.stop() + def signal_handler(signal, frame): # pylint: disable=redefined-outer-name LOGGER.warning('Terminate signal received') terminate.set() @@ -76,7 +84,9 @@ def main(): if dlt_enabled: LOGGER.info('Starting DLT functionality...') dlt_recorder = DLTRecorder() - dlt_recorder.start() + #dlt_recorder.start() + loop = asyncio.get_event_loop() + loop.run_until_complete(run_dlt_recorder(dlt_recorder)) # Wait for Ctrl+C or termination signal while not terminate.wait(timeout=1.0): pass @@ -85,7 +95,9 @@ def main(): # if topology_abstractor_enabled: # topology_abstractor.stop() if dlt_enabled: - dlt_recorder.stop() + #dlt_recorder.stop() + loop.run_until_complete(dlt_recorder.stop()) + loop.close() grpc_service.stop() remote_domain_clients.stop() diff --git a/src/interdomain/service/topology_abstractor/DltRecorder copy.py b/src/interdomain/service/topology_abstractor/DltRecorder copy.py new file mode 100644 index 000000000..0e94159c4 --- /dev/null +++ b/src/interdomain/service/topology_abstractor/DltRecorder copy.py @@ -0,0 +1,166 @@ +import logging +import threading +from typing import Dict, Optional + +from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum +from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name +from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkId, LinkEvent, TopologyId, TopologyEvent +from common.tools.context_queries.Context import create_context +from common.tools.context_queries.Device import get_uuids_of_devices_in_topology +from common.tools.context_queries.Topology import create_missing_topologies +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Context import json_context_id +from common.tools.object_factory.Device import json_device_id +from common.tools.object_factory.Topology import json_topology_id +from context.client.ContextClient import ContextClient +from context.client.EventsCollector import EventsCollector +from dlt.connector.client.DltConnectorClient import DltConnectorClient +from .DltRecordSender import DltRecordSender +from .Types import EventTypes + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) +INTERDOMAIN_TOPOLOGY_ID = TopologyId(**json_topology_id(INTERDOMAIN_TOPOLOGY_NAME, context_id=ADMIN_CONTEXT_ID)) + + +class DLTRecorder(threading.Thread): + def __init__(self) -> None: + super().__init__(daemon=True) + self.terminate = threading.Event() + self.context_client = ContextClient() + self.context_event_collector = EventsCollector(self.context_client) + self.topology_cache: Dict[str, TopologyId] = {} + + def stop(self): + self.terminate.set() + + def run(self) -> None: + self.context_client.connect() + create_context(self.context_client, DEFAULT_CONTEXT_NAME) + self.create_topologies() + self.context_event_collector.start() + + while not self.terminate.is_set(): + event = self.context_event_collector.get_event(timeout=0.1) + if event is None: + continue + LOGGER.info('Processing Event({:s})...'.format(grpc_message_to_json_string(event))) + self.update_record(event) + + self.context_event_collector.stop() + self.context_client.close() + + def create_topologies(self): + topology_uuids = [DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME] + create_missing_topologies(self.context_client, ADMIN_CONTEXT_ID, topology_uuids) + + def get_dlt_connector_client(self) -> Optional[DltConnectorClient]: + # Always enable DLT for testing + dlt_connector_client = DltConnectorClient() + dlt_connector_client.connect() + return dlt_connector_client + # env_vars = find_environment_variables([ + # get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_HOST), + # get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + # ]) + # if len(env_vars) == 2: + # dlt_connector_client = DltConnectorClient() + # dlt_connector_client.connect() + # return dlt_connector_client + # return None + + def update_record(self, event: EventTypes) -> None: + dlt_connector_client = self.get_dlt_connector_client() + dlt_record_sender = DltRecordSender(self.context_client, dlt_connector_client) + + if isinstance(event, ContextEvent): + LOGGER.debug('Processing ContextEvent({:s})'.format(grpc_message_to_json_string(event))) + LOGGER.warning('Ignoring ContextEvent({:s})'.format(grpc_message_to_json_string(event))) + + elif isinstance(event, TopologyEvent): + LOGGER.debug('Processing TopologyEvent({:s})'.format(grpc_message_to_json_string(event))) + self.process_topology_event(event, dlt_record_sender) + + elif isinstance(event, DeviceEvent): + LOGGER.debug('Processing DeviceEvent({:s})'.format(grpc_message_to_json_string(event))) + self.process_device_event(event, dlt_record_sender) + + elif isinstance(event, LinkEvent): + LOGGER.debug('Processing LinkEvent({:s})'.format(grpc_message_to_json_string(event))) + self.process_link_event(event, dlt_record_sender) + + else: + LOGGER.warning('Unsupported Event({:s})'.format(grpc_message_to_json_string(event))) + + dlt_record_sender.commit() + if dlt_connector_client is not None: + dlt_connector_client.close() + + def process_topology_event(self, event: TopologyEvent, dlt_record_sender: DltRecordSender) -> None: + topology_id = event.topology_id + topology_uuid = topology_id.topology_uuid.uuid + context_id = topology_id.context_id + context_uuid = context_id.context_uuid.uuid + topology_uuids = {DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME} + + context = self.context_client.GetContext(context_id) + context_name = context.name + + topology_details = self.context_client.GetTopologyDetails(topology_id) + topology_name = topology_details.name + + self.topology_cache[topology_uuid] = topology_id + + LOGGER.debug('TOPOLOGY Details({:s})'.format(grpc_message_to_json_string(topology_details))) + + if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ + (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): + for device in topology_details.devices: + LOGGER.debug('DEVICE_INFO_TOPO({:s})'.format(grpc_message_to_json_string(device))) + dlt_record_sender.add_device(topology_id, device) + + for link in topology_details.links: + dlt_record_sender.add_link(topology_id, link) + + else: + MSG = 'Ignoring ({:s}/{:s})({:s}/{:s}) TopologyEvent({:s})' + args = context_uuid, context_name, topology_uuid, topology_name, grpc_message_to_json_string(event) + LOGGER.warning(MSG.format(*args)) + + def find_topology_for_device(self, device_id: DeviceId) -> Optional[TopologyId]: + for topology_uuid, topology_id in self.topology_cache.items(): + details = self.context_client.GetTopologyDetails(topology_id) + for device in details.devices: + if device.device_id == device_id: + return topology_id + return None + + def find_topology_for_link(self, link_id: LinkId) -> Optional[TopologyId]: + for topology_uuid, topology_id in self.topology_cache.items(): + details = self.context_client.GetTopologyDetails(topology_id) + for link in details.links: + if link.link_id == link_id: + return topology_id + return None + + def process_device_event(self, event: DeviceEvent, dlt_record_sender: DltRecordSender) -> None: + device_id = event.device_id + device = self.context_client.GetDevice(device_id) + topology_id = self.find_topology_for_device(device_id) + if topology_id: + LOGGER.debug('DEVICE_INFO({:s}), DEVICE_ID ({:s})'.format(str(device.device_id.device_uuid.uuid), grpc_message_to_json_string(device_id))) + + dlt_record_sender.add_device(topology_id, device) + else: + LOGGER.warning(f"Topology not found for device {device_id.device_uuid.uuid}") + + def process_link_event(self, event: LinkEvent, dlt_record_sender: DltRecordSender) -> None: + link_id = event.link_id + link = self.context_client.GetLink(link_id) + topology_id = self.find_topology_for_link(link_id) + if topology_id: + dlt_record_sender.add_link(topology_id, link) + else: + LOGGER.warning(f"Topology not found for link {link_id.link_uuid.uuid}") diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 0e94159c4..96c97f5a2 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -1,10 +1,12 @@ +# DltRecorder.py + import logging -import threading +import asyncio from typing import Dict, Optional from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name -from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkId, LinkEvent, TopologyId, TopologyEvent +from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, EndPointId, Link, LinkId, LinkEvent, TopologyId, TopologyEvent from common.tools.context_queries.Context import create_context from common.tools.context_queries.Device import get_uuids_of_devices_in_topology from common.tools.context_queries.Topology import create_missing_topologies @@ -25,54 +27,47 @@ ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) INTERDOMAIN_TOPOLOGY_ID = TopologyId(**json_topology_id(INTERDOMAIN_TOPOLOGY_NAME, context_id=ADMIN_CONTEXT_ID)) -class DLTRecorder(threading.Thread): +class DLTRecorder: def __init__(self) -> None: - super().__init__(daemon=True) - self.terminate = threading.Event() + self.terminate_event = asyncio.Event() self.context_client = ContextClient() self.context_event_collector = EventsCollector(self.context_client) self.topology_cache: Dict[str, TopologyId] = {} - def stop(self): - self.terminate.set() + async def stop(self): + self.terminate_event.set() + + async def start(self) -> None: + await self.run() - def run(self) -> None: - self.context_client.connect() + async def run(self) -> None: + await self.context_client.connect() create_context(self.context_client, DEFAULT_CONTEXT_NAME) self.create_topologies() - self.context_event_collector.start() + await self.context_event_collector.start() - while not self.terminate.is_set(): - event = self.context_event_collector.get_event(timeout=0.1) + while not self.terminate_event.is_set(): + event = await self.context_event_collector.get_event(timeout=0.1) if event is None: continue LOGGER.info('Processing Event({:s})...'.format(grpc_message_to_json_string(event))) - self.update_record(event) + await self.update_record(event) - self.context_event_collector.stop() - self.context_client.close() + await self.context_event_collector.stop() + await self.context_client.close() def create_topologies(self): topology_uuids = [DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME] create_missing_topologies(self.context_client, ADMIN_CONTEXT_ID, topology_uuids) - def get_dlt_connector_client(self) -> Optional[DltConnectorClient]: + async def get_dlt_connector_client(self) -> Optional[DltConnectorClient]: # Always enable DLT for testing dlt_connector_client = DltConnectorClient() - dlt_connector_client.connect() + await dlt_connector_client.connect() return dlt_connector_client - # env_vars = find_environment_variables([ - # get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_HOST), - # get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), - # ]) - # if len(env_vars) == 2: - # dlt_connector_client = DltConnectorClient() - # dlt_connector_client.connect() - # return dlt_connector_client - # return None - - def update_record(self, event: EventTypes) -> None: - dlt_connector_client = self.get_dlt_connector_client() + + async def update_record(self, event: EventTypes) -> None: + dlt_connector_client = await self.get_dlt_connector_client() dlt_record_sender = DltRecordSender(self.context_client, dlt_connector_client) if isinstance(event, ContextEvent): @@ -81,34 +76,34 @@ class DLTRecorder(threading.Thread): elif isinstance(event, TopologyEvent): LOGGER.debug('Processing TopologyEvent({:s})'.format(grpc_message_to_json_string(event))) - self.process_topology_event(event, dlt_record_sender) + await self.process_topology_event(event, dlt_record_sender) elif isinstance(event, DeviceEvent): LOGGER.debug('Processing DeviceEvent({:s})'.format(grpc_message_to_json_string(event))) - self.process_device_event(event, dlt_record_sender) + await self.process_device_event(event, dlt_record_sender) elif isinstance(event, LinkEvent): LOGGER.debug('Processing LinkEvent({:s})'.format(grpc_message_to_json_string(event))) - self.process_link_event(event, dlt_record_sender) + await self.process_link_event(event, dlt_record_sender) else: LOGGER.warning('Unsupported Event({:s})'.format(grpc_message_to_json_string(event))) - dlt_record_sender.commit() + await dlt_record_sender.commit() if dlt_connector_client is not None: - dlt_connector_client.close() + await dlt_connector_client.close() - def process_topology_event(self, event: TopologyEvent, dlt_record_sender: DltRecordSender) -> None: + async def process_topology_event(self, event: TopologyEvent, dlt_record_sender: DltRecordSender) -> None: topology_id = event.topology_id topology_uuid = topology_id.topology_uuid.uuid context_id = topology_id.context_id context_uuid = context_id.context_uuid.uuid topology_uuids = {DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME} - context = self.context_client.GetContext(context_id) + context = await self.context_client.GetContext(context_id) context_name = context.name - topology_details = self.context_client.GetTopologyDetails(topology_id) + topology_details = await self.context_client.GetTopologyDetails(topology_id) topology_name = topology_details.name self.topology_cache[topology_uuid] = topology_id @@ -129,26 +124,26 @@ class DLTRecorder(threading.Thread): args = context_uuid, context_name, topology_uuid, topology_name, grpc_message_to_json_string(event) LOGGER.warning(MSG.format(*args)) - def find_topology_for_device(self, device_id: DeviceId) -> Optional[TopologyId]: + async def find_topology_for_device(self, device_id: DeviceId) -> Optional[TopologyId]: for topology_uuid, topology_id in self.topology_cache.items(): - details = self.context_client.GetTopologyDetails(topology_id) + details = await self.context_client.GetTopologyDetails(topology_id) for device in details.devices: if device.device_id == device_id: return topology_id return None - def find_topology_for_link(self, link_id: LinkId) -> Optional[TopologyId]: + async def find_topology_for_link(self, link_id: LinkId) -> Optional[TopologyId]: for topology_uuid, topology_id in self.topology_cache.items(): - details = self.context_client.GetTopologyDetails(topology_id) + details = await self.context_client.GetTopologyDetails(topology_id) for link in details.links: if link.link_id == link_id: return topology_id return None - def process_device_event(self, event: DeviceEvent, dlt_record_sender: DltRecordSender) -> None: + async def process_device_event(self, event: DeviceEvent, dlt_record_sender: DltRecordSender) -> None: device_id = event.device_id - device = self.context_client.GetDevice(device_id) - topology_id = self.find_topology_for_device(device_id) + device = await self.context_client.GetDevice(device_id) + topology_id = await self.find_topology_for_device(device_id) if topology_id: LOGGER.debug('DEVICE_INFO({:s}), DEVICE_ID ({:s})'.format(str(device.device_id.device_uuid.uuid), grpc_message_to_json_string(device_id))) @@ -156,10 +151,10 @@ class DLTRecorder(threading.Thread): else: LOGGER.warning(f"Topology not found for device {device_id.device_uuid.uuid}") - def process_link_event(self, event: LinkEvent, dlt_record_sender: DltRecordSender) -> None: + async def process_link_event(self, event: LinkEvent, dlt_record_sender: DltRecordSender) -> None: link_id = event.link_id - link = self.context_client.GetLink(link_id) - topology_id = self.find_topology_for_link(link_id) + link = await self.context_client.GetLink(link_id) + topology_id = await self.find_topology_for_link(link_id) if topology_id: dlt_record_sender.add_link(topology_id, link) else: -- GitLab From 29b3023e543bb63f69cb1e043faca7be1768a610 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 14:41:08 +0200 Subject: [PATCH 65/94] Debugging --- src/interdomain/service/topology_abstractor/DltRecorder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 96c97f5a2..9ed1aa750 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -6,7 +6,7 @@ from typing import Dict, Optional from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name -from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, EndPointId, Link, LinkId, LinkEvent, TopologyId, TopologyEvent +from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceId, DeviceEvent, EndPointId, Link, LinkId, LinkEvent, TopologyId, TopologyEvent from common.tools.context_queries.Context import create_context from common.tools.context_queries.Device import get_uuids_of_devices_in_topology from common.tools.context_queries.Topology import create_missing_topologies -- GitLab From 420053974ae4c4ad48e4699d85ffa31c83ba1538 Mon Sep 17 00:00:00 2001 From: diazjj Date: Mon, 29 Jul 2024 14:59:08 +0200 Subject: [PATCH 66/94] Revert "Debugging" This reverts commit 8503a835ba6a889cdb47a8666a3abb60fd63242a. --- .../connector/client/DltConnectorClient.py | 38 ++-- .../client/DltEventsCollector copy.py | 95 -------- .../connector/client/DltEventsCollector.py | 74 ++++--- src/dlt/connector/client/DltGatewayClient.py | 22 +- src/dlt/connector/client/async.py | 74 ------- .../DltConnectorServiceServicerImpl.py | 28 +-- .../DltEventDispatcher copy.py | 209 ------------------ .../event_dispatcher/DltEventDispatcher.py | 86 ++++--- src/interdomain/service/__main__.py | 16 +- .../topology_abstractor/DltRecordSender.py | 10 +- .../topology_abstractor/DltRecorder copy.py | 166 -------------- .../topology_abstractor/DltRecorder.py | 89 ++++---- 12 files changed, 186 insertions(+), 721 deletions(-) delete mode 100644 src/dlt/connector/client/DltEventsCollector copy.py delete mode 100644 src/dlt/connector/client/async.py delete mode 100644 src/dlt/connector/service/event_dispatcher/DltEventDispatcher copy.py delete mode 100644 src/interdomain/service/topology_abstractor/DltRecorder copy.py diff --git a/src/dlt/connector/client/DltConnectorClient.py b/src/dlt/connector/client/DltConnectorClient.py index 7cfb6b594..e383217d8 100644 --- a/src/dlt/connector/client/DltConnectorClient.py +++ b/src/dlt/connector/client/DltConnectorClient.py @@ -39,7 +39,7 @@ class DltConnectorClient: LOGGER.debug('Channel created') def connect(self): - self.channel = grpc.aio.insecure_channel(self.endpoint) + self.channel = grpc.insecure_channel(self.endpoint) self.stub = DltConnectorServiceStub(self.channel) def close(self): @@ -48,64 +48,64 @@ class DltConnectorClient: self.stub = None @RETRY_DECORATOR - async def RecordAll(self, request : TopologyId) -> Empty: + def RecordAll(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAll request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordAll(request) + response = self.stub.RecordAll(request) LOGGER.debug('RecordAll result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordAllDevices(self, request : TopologyId) -> Empty: + def RecordAllDevices(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAllDevices request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordAllDevices(request) + response = self.stub.RecordAllDevices(request) LOGGER.debug('RecordAllDevices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordDevice(self, request : DltDeviceId) -> Empty: + def RecordDevice(self, request : DltDeviceId) -> Empty: LOGGER.debug('RecordDevice request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordDevice(request) + response = self.stub.RecordDevice(request) LOGGER.debug('RecordDevice result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordAllLinks(self, request : TopologyId) -> Empty: + def RecordAllLinks(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAllLinks request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordAllLinks(request) + response = self.stub.RecordAllLinks(request) LOGGER.debug('RecordAllLinks result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordLink(self, request : DltLinkId) -> Empty: + def RecordLink(self, request : DltLinkId) -> Empty: LOGGER.debug('RecordLink request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordLink(request) + response = self.stub.RecordLink(request) LOGGER.debug('RecordLink result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordAllServices(self, request : TopologyId) -> Empty: + def RecordAllServices(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAllServices request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordAllServices(request) + response = self.stub.RecordAllServices(request) LOGGER.debug('RecordAllServices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordService(self, request : DltServiceId) -> Empty: + def RecordService(self, request : DltServiceId) -> Empty: LOGGER.debug('RecordService request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordService(request) + response = self.stub.RecordService(request) LOGGER.debug('RecordService result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordAllSlices(self, request : TopologyId) -> Empty: + def RecordAllSlices(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAllSlices request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordAllSlices(request) + response = self.stub.RecordAllSlices(request) LOGGER.debug('RecordAllSlices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordSlice(self, request : DltSliceId) -> Empty: + def RecordSlice(self, request : DltSliceId) -> Empty: LOGGER.debug('RecordSlice request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordSlice(request) + response = self.stub.RecordSlice(request) LOGGER.debug('RecordSlice result: {:s}'.format(grpc_message_to_json_string(response))) return response diff --git a/src/dlt/connector/client/DltEventsCollector copy.py b/src/dlt/connector/client/DltEventsCollector copy.py deleted file mode 100644 index 9fac60b7c..000000000 --- a/src/dlt/connector/client/DltEventsCollector copy.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Callable, Optional -import asyncio -import grpc, logging, queue, threading, time -from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordSubscription -from common.tools.grpc.Tools import grpc_message_to_json_string -from dlt.connector.client.DltGatewayClient import DltGatewayClient - -LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) - -# This class accepts an event_handler method as attribute that can be used to pre-process and -# filter events before they reach the events_queue. Depending on the handler, the supported -# behaviors are: -# - If the handler is not set, the events are transparently added to the events_queue. -# - If returns None for an event, the event is not stored in the events_queue. -# - If returns a DltRecordEvent object for an event, the returned event is stored in the events_queue. -# - Other combinations are not supported. - -class DltEventsCollector(threading.Thread): - def __init__( - self, dltgateway_client : DltGatewayClient, - log_events_received : bool = False, - event_handler : Optional[Callable[[DltRecordEvent], Optional[DltRecordEvent]]] = None, - ) -> None: - super().__init__(name='DltEventsCollector', daemon=True) - self._dltgateway_client = dltgateway_client - self._log_events_received = log_events_received - self._event_handler = event_handler - self._events_queue = queue.Queue() - self._terminate = threading.Event() - self._dltgateway_stream = None - - def run(self) -> None: - event_handler = self._event_handler - if event_handler is None: event_handler = lambda e: e - self._terminate.clear() - while not self._terminate.is_set(): - try: - subscription = DltRecordSubscription() # bu default subscribe to all - self._dltgateway_stream = self._dltgateway_client.SubscribeToDlt(subscription) - for event in self._dltgateway_stream: - if self._log_events_received: - LOGGER.info('[_collect] event: {:s}'.format(grpc_message_to_json_string(event))) - event = event_handler(event) - if event is None: continue - if not isinstance(event, DltRecordEvent): - # pylint: disable=broad-exception-raised - raise Exception('Unsupported return type: {:s}'.format(str(event))) - self._events_queue.put_nowait(event) - except grpc.RpcError as e: - if e.code() == grpc.StatusCode.UNAVAILABLE: # pylint: disable=no-member - time.sleep(0.5) - continue - elif e.code() == grpc.StatusCode.CANCELLED: # pylint: disable=no-member - break - else: - raise # pragma: no cover - - def get_event(self, block : bool = True, timeout : float = 0.1): - try: - return self._events_queue.get(block=block, timeout=timeout) - except queue.Empty: # pylint: disable=catching-non-exception - return None - - def get_events(self, block : bool = True, timeout : float = 0.1, count : int = None): - events = [] - if count is None: - while True: - event = self.get_event(block=block, timeout=timeout) - if event is None: break - events.append(event) - else: - for _ in range(count): - event = self.get_event(block=block, timeout=timeout) - if event is None: continue - events.append(event) - return sorted(events, key=lambda e: e.event.timestamp.timestamp) - - def stop(self): - self._terminate.set() - if self._dltgateway_stream is not None: self._dltgateway_stream.cancel() diff --git a/src/dlt/connector/client/DltEventsCollector.py b/src/dlt/connector/client/DltEventsCollector.py index 31eaf8542..e59784a4d 100644 --- a/src/dlt/connector/client/DltEventsCollector.py +++ b/src/dlt/connector/client/DltEventsCollector.py @@ -13,9 +13,7 @@ # limitations under the License. from typing import Callable, Optional -import asyncio -import grpc -import logging +import grpc, logging, queue, threading, time from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordSubscription from common.tools.grpc.Tools import grpc_message_to_json_string from dlt.connector.client.DltGatewayClient import DltGatewayClient @@ -23,70 +21,74 @@ from dlt.connector.client.DltGatewayClient import DltGatewayClient LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) -class DltEventsCollector: +# This class accepts an event_handler method as attribute that can be used to pre-process and +# filter events before they reach the events_queue. Depending on the handler, the supported +# behaviors are: +# - If the handler is not set, the events are transparently added to the events_queue. +# - If returns None for an event, the event is not stored in the events_queue. +# - If returns a DltRecordEvent object for an event, the returned event is stored in the events_queue. +# - Other combinations are not supported. + +class DltEventsCollector(threading.Thread): def __init__( - self, dltgateway_client: DltGatewayClient, - log_events_received: bool = False, - event_handler: Optional[Callable[[DltRecordEvent], Optional[DltRecordEvent]]] = None, + self, dltgateway_client : DltGatewayClient, + log_events_received : bool = False, + event_handler : Optional[Callable[[DltRecordEvent], Optional[DltRecordEvent]]] = None, ) -> None: + super().__init__(name='DltEventsCollector', daemon=True) self._dltgateway_client = dltgateway_client self._log_events_received = log_events_received self._event_handler = event_handler - self._events_queue = asyncio.Queue() - self._terminate = asyncio.Event() + self._events_queue = queue.Queue() + self._terminate = threading.Event() self._dltgateway_stream = None - async def run(self) -> None: + def run(self) -> None: event_handler = self._event_handler - if event_handler is None: - event_handler = lambda e: e + if event_handler is None: event_handler = lambda e: e self._terminate.clear() while not self._terminate.is_set(): try: - subscription = DltRecordSubscription() # by default subscribe to all - self._dltgateway_stream = await self._dltgateway_client.SubscribeToDlt(subscription) - async for event in self._dltgateway_stream: + subscription = DltRecordSubscription() # bu default subscribe to all + self._dltgateway_stream = self._dltgateway_client.SubscribeToDlt(subscription) + for event in self._dltgateway_stream: if self._log_events_received: LOGGER.info('[_collect] event: {:s}'.format(grpc_message_to_json_string(event))) event = event_handler(event) - if event is None: - continue + if event is None: continue if not isinstance(event, DltRecordEvent): + # pylint: disable=broad-exception-raised raise Exception('Unsupported return type: {:s}'.format(str(event))) - await self._events_queue.put(event) + self._events_queue.put_nowait(event) except grpc.RpcError as e: - if e.code() == grpc.StatusCode.UNAVAILABLE: # pylint: disable=no-member - await asyncio.sleep(0.5) + if e.code() == grpc.StatusCode.UNAVAILABLE: # pylint: disable=no-member + time.sleep(0.5) continue - elif e.code() == grpc.StatusCode.CANCELLED: # pylint: disable=no-member + elif e.code() == grpc.StatusCode.CANCELLED: # pylint: disable=no-member break else: - raise # pragma: no cover + raise # pragma: no cover - async def get_event(self, block: bool = True, timeout: float = 0.1): + def get_event(self, block : bool = True, timeout : float = 0.1): try: - return await asyncio.wait_for(self._events_queue.get(), timeout) - except asyncio.TimeoutError: + return self._events_queue.get(block=block, timeout=timeout) + except queue.Empty: # pylint: disable=catching-non-exception return None - async def get_events(self, block: bool = True, timeout: float = 0.1, count: int = None): + def get_events(self, block : bool = True, timeout : float = 0.1, count : int = None): events = [] if count is None: while True: - event = await self.get_event(block=block, timeout=timeout) - if event is None: - break + event = self.get_event(block=block, timeout=timeout) + if event is None: break events.append(event) else: for _ in range(count): - event = await self.get_event(block=block, timeout=timeout) - if event is None: - continue + event = self.get_event(block=block, timeout=timeout) + if event is None: continue events.append(event) return sorted(events, key=lambda e: e.event.timestamp.timestamp) - async def stop(self): + def stop(self): self._terminate.set() - if self._dltgateway_stream is not None: - await self._dltgateway_stream.cancel() - + if self._dltgateway_stream is not None: self._dltgateway_stream.cancel() diff --git a/src/dlt/connector/client/DltGatewayClient.py b/src/dlt/connector/client/DltGatewayClient.py index f9b37a2db..cde278517 100644 --- a/src/dlt/connector/client/DltGatewayClient.py +++ b/src/dlt/connector/client/DltGatewayClient.py @@ -40,7 +40,7 @@ class DltGatewayClient: LOGGER.debug('Channel created') def connect(self): - self.channel = grpc.aio.insecure_channel(self.endpoint) + self.channel = grpc.insecure_channel(self.endpoint) self.stub = DltGatewayServiceStub(self.channel) def close(self): @@ -49,36 +49,36 @@ class DltGatewayClient: self.stub = None @RETRY_DECORATOR - async def RecordToDlt(self, request : DltRecord) -> DltRecordStatus: + def RecordToDlt(self, request : DltRecord) -> DltRecordStatus: LOGGER.debug('RecordToDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordToDlt(request) + response = self.stub.RecordToDlt(request) LOGGER.debug('RecordToDlt result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def GetFromDlt(self, request : DltRecordId) -> DltRecord: + def GetFromDlt(self, request : DltRecordId) -> DltRecord: LOGGER.debug('GetFromDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.GetFromDlt(request) + response = self.stub.GetFromDlt(request) LOGGER.debug('GetFromDlt result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def SubscribeToDlt(self, request : DltRecordSubscription) -> Iterator[DltRecordEvent]: + def SubscribeToDlt(self, request : DltRecordSubscription) -> Iterator[DltRecordEvent]: LOGGER.debug('SubscribeToDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.SubscribeToDlt(request) + response = self.stub.SubscribeToDlt(request) LOGGER.debug('SubscribeToDlt result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def GetDltStatus(self, request : TeraFlowController) -> DltPeerStatus: + def GetDltStatus(self, request : TeraFlowController) -> DltPeerStatus: LOGGER.debug('GetDltStatus request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.GetDltStatus(request) + response = self.stub.GetDltStatus(request) LOGGER.debug('GetDltStatus result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def GetDltPeers(self, request : Empty) -> DltPeerStatusList: + def GetDltPeers(self, request : Empty) -> DltPeerStatusList: LOGGER.debug('GetDltPeers request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.GetDltPeers(request) + response = self.stub.GetDltPeers(request) LOGGER.debug('GetDltPeers result: {:s}'.format(grpc_message_to_json_string(response))) return response diff --git a/src/dlt/connector/client/async.py b/src/dlt/connector/client/async.py deleted file mode 100644 index e38f124c2..000000000 --- a/src/dlt/connector/client/async.py +++ /dev/null @@ -1,74 +0,0 @@ -# DltGatewayClient.py - -import grpc -import logging -from typing import Iterator -from common.proto.context_pb2 import Empty, TeraFlowController -from common.proto.dlt_gateway_pb2 import ( - DltPeerStatus, DltPeerStatusList, DltRecord, DltRecordEvent, DltRecordId, DltRecordStatus, DltRecordSubscription -) -from common.proto.dlt_gateway_pb2_grpc import DltGatewayServiceStub -from common.tools.client.RetryDecorator import retry, delay_exponential -from common.tools.grpc.Tools import grpc_message_to_json_string -from dlt.connector.Config import DLT_GATEWAY_HOST, DLT_GATEWAY_PORT - -LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) -MAX_RETRIES = 15 -DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) -RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') - -class DltGatewayClient: - def __init__(self, host=None, port=None): - if not host: host = DLT_GATEWAY_HOST - if not port: port = DLT_GATEWAY_PORT - self.endpoint = '{:s}:{:s}'.format(str(host), str(port)) - LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) - self.channel = None - self.stub = None - self.connect() - LOGGER.debug('Channel created') - - def connect(self): - self.channel = grpc.aio.insecure_channel(self.endpoint) - self.stub = DltGatewayServiceStub(self.channel) - - def close(self): - if self.channel is not None: self.channel.close() - self.channel = None - self.stub = None - - @RETRY_DECORATOR - async def RecordToDlt(self, request: DltRecord) -> DltRecordStatus: - LOGGER.debug('RecordToDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordToDlt(request) - LOGGER.debug('RecordToDlt result: {:s}'.format(grpc_message_to_json_string(response))) - return response - - @RETRY_DECORATOR - async def GetFromDlt(self, request: DltRecordId) -> DltRecord: - LOGGER.debug('GetFromDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.GetFromDlt(request) - LOGGER.debug('GetFromDlt result: {:s}'.format(grpc_message_to_json_string(response))) - return response - - @RETRY_DECORATOR - async def SubscribeToDlt(self, request: DltRecordSubscription) -> Iterator[DltRecordEvent]: - LOGGER.debug('SubscribeToDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.SubscribeToDlt(request) - LOGGER.debug('SubscribeToDlt result: {:s}'.format(grpc_message_to_json_string(response))) - return response - - @RETRY_DECORATOR - async def GetDltStatus(self, request: TeraFlowController) -> DltPeerStatus: - LOGGER.debug('GetDltStatus request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.GetDltStatus(request) - LOGGER.debug('GetDltStatus result: {:s}'.format(grpc_message_to_json_string(response))) - return response - - @RETRY_DECORATOR - async def GetDltPeers(self, request: Empty) -> DltPeerStatusList: - LOGGER.debug('GetDltPeers request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.GetDltPeers(request) - LOGGER.debug('GetDltPeers result: {:s}'.format(grpc_message_to_json_string(response))) - return response diff --git a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py index df5e8fd08..c05d46b48 100644 --- a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py +++ b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py @@ -35,22 +35,22 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): LOGGER.debug('Servicer Created') @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - async def RecordAll(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + def RecordAll(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - async def RecordAllDevices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + def RecordAllDevices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - async def RecordDevice(self, request : DltDeviceId, context : grpc.ServicerContext) -> Empty: + def RecordDevice(self, request : DltDeviceId, context : grpc.ServicerContext) -> Empty: data_json = None if not request.delete: context_client = ContextClient() device = context_client.GetDevice(request.device_id) data_json = grpc_message_to_json_string(device) - await self._record_entity( + self._record_entity( request.topology_id.topology_uuid.uuid, DltRecordTypeEnum.DLTRECORDTYPE_DEVICE, request.device_id.device_uuid.uuid, request.delete, data_json) return Empty() @@ -60,53 +60,53 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - async def RecordLink(self, request : DltLinkId, context : grpc.ServicerContext) -> Empty: + def RecordLink(self, request : DltLinkId, context : grpc.ServicerContext) -> Empty: data_json = None if not request.delete: context_client = ContextClient() link = context_client.GetLink(request.link_id) data_json = grpc_message_to_json_string(link) - await self._record_entity( + self._record_entity( request.topology_id.topology_uuid.uuid, DltRecordTypeEnum.DLTRECORDTYPE_LINK, request.link_id.link_uuid.uuid, request.delete, data_json) return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - async def RecordAllServices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + def RecordAllServices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - async def RecordService(self, request : DltServiceId, context : grpc.ServicerContext) -> Empty: + def RecordService(self, request : DltServiceId, context : grpc.ServicerContext) -> Empty: data_json = None if not request.delete: context_client = ContextClient() service = context_client.GetService(request.service_id) data_json = grpc_message_to_json_string(service) - await self._record_entity( + self._record_entity( request.topology_id.topology_uuid.uuid, DltRecordTypeEnum.DLTRECORDTYPE_SERVICE, request.service_id.service_uuid.uuid, request.delete, data_json) return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - async def RecordAllSlices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + def RecordAllSlices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - async def RecordSlice(self, request : DltSliceId, context : grpc.ServicerContext) -> Empty: + def RecordSlice(self, request : DltSliceId, context : grpc.ServicerContext) -> Empty: data_json = None if not request.delete: context_client = ContextClient() slice_ = context_client.GetSlice(request.slice_id) data_json = grpc_message_to_json_string(slice_) - await self._record_entity( + self._record_entity( request.topology_id.topology_uuid.uuid, DltRecordTypeEnum.DLTRECORDTYPE_SLICE, request.slice_id.slice_uuid.uuid, request.delete, data_json) return Empty() - async def _record_entity( + def _record_entity( self, dlt_domain_uuid : str, dlt_record_type : DltRecordTypeEnum, dlt_record_uuid : str, delete : bool, data_json : Optional[str] = None ) -> None: @@ -143,6 +143,6 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): str_dlt_record = grpc_message_to_json_string(dlt_record) LOGGER.debug('[_record_entity] sent dlt_record = {:s}'.format(str_dlt_record)) - dlt_record_status = await dltgateway_client.RecordToDlt(dlt_record) + dlt_record_status = dltgateway_client.RecordToDlt(dlt_record) str_dlt_record_status = grpc_message_to_json_string(dlt_record_status) LOGGER.debug('[_record_entity] recv dlt_record_status = {:s}'.format(str_dlt_record_status)) diff --git a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher copy.py b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher copy.py deleted file mode 100644 index 779bae9c1..000000000 --- a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher copy.py +++ /dev/null @@ -1,209 +0,0 @@ -# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import grpc, json, logging, threading -from typing import Any, Dict, Set -from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME -from common.proto.context_pb2 import ContextId, Device, EventTypeEnum, Link, Slice, TopologyId -from common.proto.dlt_connector_pb2 import DltSliceId -from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordOperationEnum, DltRecordTypeEnum -from common.tools.context_queries.Context import create_context -from common.tools.context_queries.Device import add_device_to_topology -from common.tools.context_queries.Link import add_link_to_topology -from common.tools.context_queries.Topology import create_topology -from common.tools.grpc.Tools import grpc_message_to_json_string -from common.tools.object_factory.Context import json_context_id -from common.tools.object_factory.Topology import json_topology_id -from context.client.ContextClient import ContextClient -from dlt.connector.client.DltConnectorClient import DltConnectorClient -from dlt.connector.client.DltEventsCollector import DltEventsCollector -from dlt.connector.client.DltGatewayClient import DltGatewayClient -from interdomain.client.InterdomainClient import InterdomainClient - -LOGGER = logging.getLogger(__name__) - -GET_EVENT_TIMEOUT = 0.5 - -ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) - -class Clients: - def __init__(self) -> None: - self.context_client = ContextClient() - self.dlt_connector_client = DltConnectorClient() - self.dlt_gateway_client = DltGatewayClient() - self.interdomain_client = InterdomainClient() - - def close(self) -> None: - self.interdomain_client.close() - self.dlt_gateway_client.close() - self.dlt_connector_client.close() - self.context_client.close() - -class DltEventDispatcher(threading.Thread): - def __init__(self) -> None: - LOGGER.debug('Creating connector...') - super().__init__(name='DltEventDispatcher', daemon=True) - self._terminate = threading.Event() - LOGGER.debug('Connector created') - - def start(self) -> None: - self._terminate.clear() - return super().start() - - def stop(self): - self._terminate.set() - - def run(self) -> None: - clients = Clients() - create_context(clients.context_client, DEFAULT_CONTEXT_NAME) - create_topology(clients.context_client, DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME) - create_topology(clients.context_client, DEFAULT_CONTEXT_NAME, INTERDOMAIN_TOPOLOGY_NAME) - - dlt_events_collector = DltEventsCollector(clients.dlt_gateway_client, log_events_received=True) - dlt_events_collector.start() - - while not self._terminate.is_set(): - event = dlt_events_collector.get_event(block=True, timeout=GET_EVENT_TIMEOUT) - if event is None: continue - - existing_topology_ids = clients.context_client.ListTopologyIds(ADMIN_CONTEXT_ID) - local_domain_uuids = { - topology_id.topology_uuid.uuid for topology_id in existing_topology_ids.topology_ids - } - local_domain_uuids.discard(DEFAULT_TOPOLOGY_NAME) - local_domain_uuids.discard(INTERDOMAIN_TOPOLOGY_NAME) - - self.dispatch_event(clients, local_domain_uuids, event) - - dlt_events_collector.stop() - clients.close() - - def dispatch_event(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: - record_type : DltRecordTypeEnum = event.record_id.type # {UNDEFINED/CONTEXT/TOPOLOGY/DEVICE/LINK/SERVICE/SLICE} - if record_type == DltRecordTypeEnum.DLTRECORDTYPE_DEVICE: - self._dispatch_device(clients, local_domain_uuids, event) - elif record_type == DltRecordTypeEnum.DLTRECORDTYPE_LINK: - self._dispatch_link(clients, local_domain_uuids, event) - elif record_type == DltRecordTypeEnum.DLTRECORDTYPE_SLICE: - self._dispatch_slice(clients, local_domain_uuids, event) - else: - raise NotImplementedError('EventType: {:s}'.format(grpc_message_to_json_string(event))) - - def _dispatch_device(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: - domain_uuid : str = event.record_id.domain_uuid.uuid - - if domain_uuid in local_domain_uuids: - MSG = '[_dispatch_device] Ignoring DLT event received (local): {:s}' - LOGGER.info(MSG.format(grpc_message_to_json_string(event))) - return - - MSG = '[_dispatch_device] DLT event received (remote): {:s}' - LOGGER.info(MSG.format(grpc_message_to_json_string(event))) - - event_type : EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} - if event_type in {EventTypeEnum.EVENTTYPE_CREATE, EventTypeEnum.EVENTTYPE_UPDATE}: - LOGGER.info('[_dispatch_device] event.record_id={:s}'.format(grpc_message_to_json_string(event.record_id))) - record = clients.dlt_gateway_client.GetFromDlt(event.record_id) - LOGGER.info('[_dispatch_device] record={:s}'.format(grpc_message_to_json_string(record))) - - create_context(clients.context_client, domain_uuid) - create_topology(clients.context_client, domain_uuid, DEFAULT_TOPOLOGY_NAME) - device = Device(**json.loads(record.data_json)) - clients.context_client.SetDevice(device) - device_uuid = device.device_id.device_uuid.uuid # pylint: disable=no-member - add_device_to_topology(clients.context_client, ADMIN_CONTEXT_ID, INTERDOMAIN_TOPOLOGY_NAME, device_uuid) - domain_context_id = ContextId(**json_context_id(domain_uuid)) - add_device_to_topology(clients.context_client, domain_context_id, DEFAULT_TOPOLOGY_NAME, device_uuid) - elif event_type in {EventTypeEnum.EVENTTYPE_DELETE}: - raise NotImplementedError('Delete Device') - - def _dispatch_link(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: - domain_uuid : str = event.record_id.domain_uuid.uuid - - if domain_uuid in local_domain_uuids: - MSG = '[_dispatch_link] Ignoring DLT event received (local): {:s}' - LOGGER.info(MSG.format(grpc_message_to_json_string(event))) - return - - MSG = '[_dispatch_link] DLT event received (remote): {:s}' - LOGGER.info(MSG.format(grpc_message_to_json_string(event))) - - event_type : EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} - if event_type in {EventTypeEnum.EVENTTYPE_CREATE, EventTypeEnum.EVENTTYPE_UPDATE}: - LOGGER.info('[_dispatch_link] event.record_id={:s}'.format(grpc_message_to_json_string(event.record_id))) - record = clients.dlt_gateway_client.GetFromDlt(event.record_id) - LOGGER.info('[_dispatch_link] record={:s}'.format(grpc_message_to_json_string(record))) - - link = Link(**json.loads(record.data_json)) - clients.context_client.SetLink(link) - link_uuid = link.link_id.link_uuid.uuid # pylint: disable=no-member - add_link_to_topology(clients.context_client, ADMIN_CONTEXT_ID, INTERDOMAIN_TOPOLOGY_NAME, link_uuid) - elif event_type in {EventTypeEnum.EVENTTYPE_DELETE}: - raise NotImplementedError('Delete Link') - - def _dispatch_slice(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: - event_type : EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} - domain_uuid : str = event.record_id.domain_uuid.uuid - - LOGGER.info('[_dispatch_slice] event.record_id={:s}'.format(grpc_message_to_json_string(event.record_id))) - record = clients.dlt_gateway_client.GetFromDlt(event.record_id) - LOGGER.info('[_dispatch_slice] record={:s}'.format(grpc_message_to_json_string(record))) - - slice_ = Slice(**json.loads(record.data_json)) - - context_uuid = slice_.slice_id.context_id.context_uuid.uuid - owner_uuid = slice_.slice_owner.owner_uuid.uuid - create_context(clients.context_client, context_uuid) - create_topology(clients.context_client, context_uuid, DEFAULT_TOPOLOGY_NAME) - - if domain_uuid in local_domain_uuids: - # it is for "me" - if event_type in {EventTypeEnum.EVENTTYPE_CREATE, EventTypeEnum.EVENTTYPE_UPDATE}: - try: - db_slice = clients.context_client.GetSlice(slice_.slice_id) - # exists - db_json_slice = grpc_message_to_json_string(db_slice) - except grpc.RpcError: - # not exists - db_json_slice = None - - _json_slice = grpc_message_to_json_string(slice_) - if db_json_slice != _json_slice: - # not exists or is different... - slice_id = clients.interdomain_client.RequestSlice(slice_) - topology_id = TopologyId(**json_topology_id(domain_uuid)) - dlt_slice_id = DltSliceId() - dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member - dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member - clients.dlt_connector_client.RecordSlice(dlt_slice_id) - - elif event_type in {EventTypeEnum.EVENTTYPE_DELETE}: - raise NotImplementedError('Delete Slice') - elif owner_uuid in local_domain_uuids: - # it is owned by me - # just update it locally - LOGGER.info('[_dispatch_slice] updating locally') - - local_slice = Slice() - local_slice.CopyFrom(slice_) - - # pylint: disable=no-member - del local_slice.slice_service_ids[:] # they are from remote domains so will not be present locally - del local_slice.slice_subslice_ids[:] # they are from remote domains so will not be present locally - - clients.context_client.SetSlice(local_slice) - else: - MSG = '[_dispatch_slice] Ignoring DLT event received (remote): {:s}' - LOGGER.info(MSG.format(grpc_message_to_json_string(event))) - diff --git a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py index 76104e2b7..779bae9c1 100644 --- a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py +++ b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py @@ -1,11 +1,23 @@ -import asyncio -import logging -import json +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import grpc, json, logging, threading from typing import Any, Dict, Set from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME from common.proto.context_pb2 import ContextId, Device, EventTypeEnum, Link, Slice, TopologyId from common.proto.dlt_connector_pb2 import DltSliceId -from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordTypeEnum +from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordOperationEnum, DltRecordTypeEnum from common.tools.context_queries.Context import create_context from common.tools.context_queries.Device import add_device_to_topology from common.tools.context_queries.Link import add_link_to_topology @@ -38,30 +50,31 @@ class Clients: self.dlt_connector_client.close() self.context_client.close() -class DltEventDispatcher: +class DltEventDispatcher(threading.Thread): def __init__(self) -> None: LOGGER.debug('Creating connector...') - self._terminate = asyncio.Event() + super().__init__(name='DltEventDispatcher', daemon=True) + self._terminate = threading.Event() LOGGER.debug('Connector created') - async def start(self) -> None: + def start(self) -> None: self._terminate.clear() - await self.run() + return super().start() - async def stop(self): + def stop(self): self._terminate.set() - async def run(self) -> None: + def run(self) -> None: clients = Clients() create_context(clients.context_client, DEFAULT_CONTEXT_NAME) create_topology(clients.context_client, DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME) create_topology(clients.context_client, DEFAULT_CONTEXT_NAME, INTERDOMAIN_TOPOLOGY_NAME) dlt_events_collector = DltEventsCollector(clients.dlt_gateway_client, log_events_received=True) - await dlt_events_collector.run() + dlt_events_collector.start() while not self._terminate.is_set(): - event = await dlt_events_collector.get_event(block=True, timeout=GET_EVENT_TIMEOUT) + event = dlt_events_collector.get_event(block=True, timeout=GET_EVENT_TIMEOUT) if event is None: continue existing_topology_ids = clients.context_client.ListTopologyIds(ADMIN_CONTEXT_ID) @@ -71,24 +84,24 @@ class DltEventDispatcher: local_domain_uuids.discard(DEFAULT_TOPOLOGY_NAME) local_domain_uuids.discard(INTERDOMAIN_TOPOLOGY_NAME) - await self.dispatch_event(clients, local_domain_uuids, event) + self.dispatch_event(clients, local_domain_uuids, event) - await dlt_events_collector.stop() + dlt_events_collector.stop() clients.close() - async def dispatch_event(self, clients: Clients, local_domain_uuids: Set[str], event: DltRecordEvent) -> None: - record_type: DltRecordTypeEnum = event.record_id.type # {UNDEFINED/CONTEXT/TOPOLOGY/DEVICE/LINK/SERVICE/SLICE} + def dispatch_event(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: + record_type : DltRecordTypeEnum = event.record_id.type # {UNDEFINED/CONTEXT/TOPOLOGY/DEVICE/LINK/SERVICE/SLICE} if record_type == DltRecordTypeEnum.DLTRECORDTYPE_DEVICE: - await self._dispatch_device(clients, local_domain_uuids, event) + self._dispatch_device(clients, local_domain_uuids, event) elif record_type == DltRecordTypeEnum.DLTRECORDTYPE_LINK: - await self._dispatch_link(clients, local_domain_uuids, event) + self._dispatch_link(clients, local_domain_uuids, event) elif record_type == DltRecordTypeEnum.DLTRECORDTYPE_SLICE: - await self._dispatch_slice(clients, local_domain_uuids, event) + self._dispatch_slice(clients, local_domain_uuids, event) else: raise NotImplementedError('EventType: {:s}'.format(grpc_message_to_json_string(event))) - async def _dispatch_device(self, clients: Clients, local_domain_uuids: Set[str], event: DltRecordEvent) -> None: - domain_uuid: str = event.record_id.domain_uuid.uuid + def _dispatch_device(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: + domain_uuid : str = event.record_id.domain_uuid.uuid if domain_uuid in local_domain_uuids: MSG = '[_dispatch_device] Ignoring DLT event received (local): {:s}' @@ -98,25 +111,25 @@ class DltEventDispatcher: MSG = '[_dispatch_device] DLT event received (remote): {:s}' LOGGER.info(MSG.format(grpc_message_to_json_string(event))) - event_type: EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} + event_type : EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} if event_type in {EventTypeEnum.EVENTTYPE_CREATE, EventTypeEnum.EVENTTYPE_UPDATE}: LOGGER.info('[_dispatch_device] event.record_id={:s}'.format(grpc_message_to_json_string(event.record_id))) - record = await clients.dlt_gateway_client.GetFromDlt(event.record_id) + record = clients.dlt_gateway_client.GetFromDlt(event.record_id) LOGGER.info('[_dispatch_device] record={:s}'.format(grpc_message_to_json_string(record))) create_context(clients.context_client, domain_uuid) create_topology(clients.context_client, domain_uuid, DEFAULT_TOPOLOGY_NAME) device = Device(**json.loads(record.data_json)) clients.context_client.SetDevice(device) - device_uuid = device.device_id.device_uuid.uuid # pylint: disable=no-member + device_uuid = device.device_id.device_uuid.uuid # pylint: disable=no-member add_device_to_topology(clients.context_client, ADMIN_CONTEXT_ID, INTERDOMAIN_TOPOLOGY_NAME, device_uuid) domain_context_id = ContextId(**json_context_id(domain_uuid)) add_device_to_topology(clients.context_client, domain_context_id, DEFAULT_TOPOLOGY_NAME, device_uuid) elif event_type in {EventTypeEnum.EVENTTYPE_DELETE}: raise NotImplementedError('Delete Device') - async def _dispatch_link(self, clients: Clients, local_domain_uuids: Set[str], event: DltRecordEvent) -> None: - domain_uuid: str = event.record_id.domain_uuid.uuid + def _dispatch_link(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: + domain_uuid : str = event.record_id.domain_uuid.uuid if domain_uuid in local_domain_uuids: MSG = '[_dispatch_link] Ignoring DLT event received (local): {:s}' @@ -126,25 +139,25 @@ class DltEventDispatcher: MSG = '[_dispatch_link] DLT event received (remote): {:s}' LOGGER.info(MSG.format(grpc_message_to_json_string(event))) - event_type: EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} + event_type : EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} if event_type in {EventTypeEnum.EVENTTYPE_CREATE, EventTypeEnum.EVENTTYPE_UPDATE}: LOGGER.info('[_dispatch_link] event.record_id={:s}'.format(grpc_message_to_json_string(event.record_id))) - record = await clients.dlt_gateway_client.GetFromDlt(event.record_id) + record = clients.dlt_gateway_client.GetFromDlt(event.record_id) LOGGER.info('[_dispatch_link] record={:s}'.format(grpc_message_to_json_string(record))) link = Link(**json.loads(record.data_json)) clients.context_client.SetLink(link) - link_uuid = link.link_id.link_uuid.uuid # pylint: disable=no-member + link_uuid = link.link_id.link_uuid.uuid # pylint: disable=no-member add_link_to_topology(clients.context_client, ADMIN_CONTEXT_ID, INTERDOMAIN_TOPOLOGY_NAME, link_uuid) elif event_type in {EventTypeEnum.EVENTTYPE_DELETE}: raise NotImplementedError('Delete Link') - async def _dispatch_slice(self, clients: Clients, local_domain_uuids: Set[str], event: DltRecordEvent) -> None: - event_type: EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} - domain_uuid: str = event.record_id.domain_uuid.uuid + def _dispatch_slice(self, clients : Clients, local_domain_uuids : Set[str], event : DltRecordEvent) -> None: + event_type : EventTypeEnum = event.event.event_type # {UNDEFINED/CREATE/UPDATE/REMOVE} + domain_uuid : str = event.record_id.domain_uuid.uuid LOGGER.info('[_dispatch_slice] event.record_id={:s}'.format(grpc_message_to_json_string(event.record_id))) - record = await clients.dlt_gateway_client.GetFromDlt(event.record_id) + record = clients.dlt_gateway_client.GetFromDlt(event.record_id) LOGGER.info('[_dispatch_slice] record={:s}'.format(grpc_message_to_json_string(record))) slice_ = Slice(**json.loads(record.data_json)) @@ -172,7 +185,7 @@ class DltEventDispatcher: topology_id = TopologyId(**json_topology_id(domain_uuid)) dlt_slice_id = DltSliceId() dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member - dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member + dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member clients.dlt_connector_client.RecordSlice(dlt_slice_id) elif event_type in {EventTypeEnum.EVENTTYPE_DELETE}: @@ -186,10 +199,11 @@ class DltEventDispatcher: local_slice.CopyFrom(slice_) # pylint: disable=no-member - del local_slice.slice_service_ids[:] # they are from remote domains so will not be present locally - del local_slice.slice_subslice_ids[:] # they are from remote domains so will not be present locally + del local_slice.slice_service_ids[:] # they are from remote domains so will not be present locally + del local_slice.slice_subslice_ids[:] # they are from remote domains so will not be present locally clients.context_client.SetSlice(local_slice) else: MSG = '[_dispatch_slice] Ignoring DLT event received (remote): {:s}' LOGGER.info(MSG.format(grpc_message_to_json_string(event))) + diff --git a/src/interdomain/service/__main__.py b/src/interdomain/service/__main__.py index 3f5ccd183..8c392821e 100644 --- a/src/interdomain/service/__main__.py +++ b/src/interdomain/service/__main__.py @@ -13,8 +13,6 @@ # limitations under the License. import logging, signal, sys, threading -import asyncio - from prometheus_client import start_http_server from common.Constants import ServiceNameEnum from common.Settings import ( @@ -29,12 +27,6 @@ from .RemoteDomainClients import RemoteDomainClients terminate = threading.Event() LOGGER : logging.Logger = None - -async def run_dlt_recorder(dlt_recorder): - await dlt_recorder.start() - await terminate.wait() - await dlt_recorder.stop() - def signal_handler(signal, frame): # pylint: disable=redefined-outer-name LOGGER.warning('Terminate signal received') terminate.set() @@ -84,9 +76,7 @@ def main(): if dlt_enabled: LOGGER.info('Starting DLT functionality...') dlt_recorder = DLTRecorder() - #dlt_recorder.start() - loop = asyncio.get_event_loop() - loop.run_until_complete(run_dlt_recorder(dlt_recorder)) + dlt_recorder.start() # Wait for Ctrl+C or termination signal while not terminate.wait(timeout=1.0): pass @@ -95,9 +85,7 @@ def main(): # if topology_abstractor_enabled: # topology_abstractor.stop() if dlt_enabled: - #dlt_recorder.stop() - loop.run_until_complete(dlt_recorder.stop()) - loop.close() + dlt_recorder.stop() grpc_service.stop() remote_domain_clients.stop() diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index c17878e0e..cfa51c928 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -62,7 +62,7 @@ class DltRecordSender: record_uuid = '{:s}:slice:{:s}/{:s}'.format(topology_uuid, context_uuid, slice_uuid) self._add_record(record_uuid, (topology_id, slice_)) - async def commit(self) -> None: + def commit(self) -> None: for dlt_record_uuid in self.dlt_record_uuids: topology_id,dlt_record = self.dlt_record_uuid_to_data[dlt_record_uuid] if isinstance(dlt_record, Device): @@ -73,7 +73,7 @@ class DltRecordSender: dlt_device_id = DltDeviceId() dlt_device_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_device_id.device_id.CopyFrom(device_id) # pylint: disable=no-member - await self.dlt_connector_client.RecordDevice(dlt_device_id) + self.dlt_connector_client.RecordDevice(dlt_device_id) elif isinstance(dlt_record, Link): #link_id = self.context_client.SetLink(dlt_record) link_id = dlt_record.link_id @@ -81,7 +81,7 @@ class DltRecordSender: dlt_link_id = DltLinkId() dlt_link_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_link_id.link_id.CopyFrom(link_id) # pylint: disable=no-member - await self.dlt_connector_client.RecordLink(dlt_link_id) + self.dlt_connector_client.RecordLink(dlt_link_id) elif isinstance(dlt_record, Service): #service_id = self.context_client.SetService(dlt_record) service_id = dlt_record.service_id @@ -89,7 +89,7 @@ class DltRecordSender: dlt_service_id = DltServiceId() dlt_service_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_service_id.service_id.CopyFrom(service_id) # pylint: disable=no-member - await self.dlt_connector_client.RecordService(dlt_service_id) + self.dlt_connector_client.RecordService(dlt_service_id) elif isinstance(dlt_record, Slice): #slice_id = self.context_client.SetSlice(dlt_record) slice_id = dlt_record.slice_id @@ -97,6 +97,6 @@ class DltRecordSender: dlt_slice_id = DltSliceId() dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member - await self.dlt_connector_client.RecordSlice(dlt_slice_id) + self.dlt_connector_client.RecordSlice(dlt_slice_id) else: LOGGER.error('Unsupported Record({:s})'.format(str(dlt_record))) diff --git a/src/interdomain/service/topology_abstractor/DltRecorder copy.py b/src/interdomain/service/topology_abstractor/DltRecorder copy.py deleted file mode 100644 index 0e94159c4..000000000 --- a/src/interdomain/service/topology_abstractor/DltRecorder copy.py +++ /dev/null @@ -1,166 +0,0 @@ -import logging -import threading -from typing import Dict, Optional - -from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum -from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name -from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkId, LinkEvent, TopologyId, TopologyEvent -from common.tools.context_queries.Context import create_context -from common.tools.context_queries.Device import get_uuids_of_devices_in_topology -from common.tools.context_queries.Topology import create_missing_topologies -from common.tools.grpc.Tools import grpc_message_to_json_string -from common.tools.object_factory.Context import json_context_id -from common.tools.object_factory.Device import json_device_id -from common.tools.object_factory.Topology import json_topology_id -from context.client.ContextClient import ContextClient -from context.client.EventsCollector import EventsCollector -from dlt.connector.client.DltConnectorClient import DltConnectorClient -from .DltRecordSender import DltRecordSender -from .Types import EventTypes - -LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) - -ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) -INTERDOMAIN_TOPOLOGY_ID = TopologyId(**json_topology_id(INTERDOMAIN_TOPOLOGY_NAME, context_id=ADMIN_CONTEXT_ID)) - - -class DLTRecorder(threading.Thread): - def __init__(self) -> None: - super().__init__(daemon=True) - self.terminate = threading.Event() - self.context_client = ContextClient() - self.context_event_collector = EventsCollector(self.context_client) - self.topology_cache: Dict[str, TopologyId] = {} - - def stop(self): - self.terminate.set() - - def run(self) -> None: - self.context_client.connect() - create_context(self.context_client, DEFAULT_CONTEXT_NAME) - self.create_topologies() - self.context_event_collector.start() - - while not self.terminate.is_set(): - event = self.context_event_collector.get_event(timeout=0.1) - if event is None: - continue - LOGGER.info('Processing Event({:s})...'.format(grpc_message_to_json_string(event))) - self.update_record(event) - - self.context_event_collector.stop() - self.context_client.close() - - def create_topologies(self): - topology_uuids = [DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME] - create_missing_topologies(self.context_client, ADMIN_CONTEXT_ID, topology_uuids) - - def get_dlt_connector_client(self) -> Optional[DltConnectorClient]: - # Always enable DLT for testing - dlt_connector_client = DltConnectorClient() - dlt_connector_client.connect() - return dlt_connector_client - # env_vars = find_environment_variables([ - # get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_HOST), - # get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), - # ]) - # if len(env_vars) == 2: - # dlt_connector_client = DltConnectorClient() - # dlt_connector_client.connect() - # return dlt_connector_client - # return None - - def update_record(self, event: EventTypes) -> None: - dlt_connector_client = self.get_dlt_connector_client() - dlt_record_sender = DltRecordSender(self.context_client, dlt_connector_client) - - if isinstance(event, ContextEvent): - LOGGER.debug('Processing ContextEvent({:s})'.format(grpc_message_to_json_string(event))) - LOGGER.warning('Ignoring ContextEvent({:s})'.format(grpc_message_to_json_string(event))) - - elif isinstance(event, TopologyEvent): - LOGGER.debug('Processing TopologyEvent({:s})'.format(grpc_message_to_json_string(event))) - self.process_topology_event(event, dlt_record_sender) - - elif isinstance(event, DeviceEvent): - LOGGER.debug('Processing DeviceEvent({:s})'.format(grpc_message_to_json_string(event))) - self.process_device_event(event, dlt_record_sender) - - elif isinstance(event, LinkEvent): - LOGGER.debug('Processing LinkEvent({:s})'.format(grpc_message_to_json_string(event))) - self.process_link_event(event, dlt_record_sender) - - else: - LOGGER.warning('Unsupported Event({:s})'.format(grpc_message_to_json_string(event))) - - dlt_record_sender.commit() - if dlt_connector_client is not None: - dlt_connector_client.close() - - def process_topology_event(self, event: TopologyEvent, dlt_record_sender: DltRecordSender) -> None: - topology_id = event.topology_id - topology_uuid = topology_id.topology_uuid.uuid - context_id = topology_id.context_id - context_uuid = context_id.context_uuid.uuid - topology_uuids = {DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME} - - context = self.context_client.GetContext(context_id) - context_name = context.name - - topology_details = self.context_client.GetTopologyDetails(topology_id) - topology_name = topology_details.name - - self.topology_cache[topology_uuid] = topology_id - - LOGGER.debug('TOPOLOGY Details({:s})'.format(grpc_message_to_json_string(topology_details))) - - if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ - (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): - for device in topology_details.devices: - LOGGER.debug('DEVICE_INFO_TOPO({:s})'.format(grpc_message_to_json_string(device))) - dlt_record_sender.add_device(topology_id, device) - - for link in topology_details.links: - dlt_record_sender.add_link(topology_id, link) - - else: - MSG = 'Ignoring ({:s}/{:s})({:s}/{:s}) TopologyEvent({:s})' - args = context_uuid, context_name, topology_uuid, topology_name, grpc_message_to_json_string(event) - LOGGER.warning(MSG.format(*args)) - - def find_topology_for_device(self, device_id: DeviceId) -> Optional[TopologyId]: - for topology_uuid, topology_id in self.topology_cache.items(): - details = self.context_client.GetTopologyDetails(topology_id) - for device in details.devices: - if device.device_id == device_id: - return topology_id - return None - - def find_topology_for_link(self, link_id: LinkId) -> Optional[TopologyId]: - for topology_uuid, topology_id in self.topology_cache.items(): - details = self.context_client.GetTopologyDetails(topology_id) - for link in details.links: - if link.link_id == link_id: - return topology_id - return None - - def process_device_event(self, event: DeviceEvent, dlt_record_sender: DltRecordSender) -> None: - device_id = event.device_id - device = self.context_client.GetDevice(device_id) - topology_id = self.find_topology_for_device(device_id) - if topology_id: - LOGGER.debug('DEVICE_INFO({:s}), DEVICE_ID ({:s})'.format(str(device.device_id.device_uuid.uuid), grpc_message_to_json_string(device_id))) - - dlt_record_sender.add_device(topology_id, device) - else: - LOGGER.warning(f"Topology not found for device {device_id.device_uuid.uuid}") - - def process_link_event(self, event: LinkEvent, dlt_record_sender: DltRecordSender) -> None: - link_id = event.link_id - link = self.context_client.GetLink(link_id) - topology_id = self.find_topology_for_link(link_id) - if topology_id: - dlt_record_sender.add_link(topology_id, link) - else: - LOGGER.warning(f"Topology not found for link {link_id.link_uuid.uuid}") diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 9ed1aa750..0e94159c4 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -1,12 +1,10 @@ -# DltRecorder.py - import logging -import asyncio +import threading from typing import Dict, Optional from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name -from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceId, DeviceEvent, EndPointId, Link, LinkId, LinkEvent, TopologyId, TopologyEvent +from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkId, LinkEvent, TopologyId, TopologyEvent from common.tools.context_queries.Context import create_context from common.tools.context_queries.Device import get_uuids_of_devices_in_topology from common.tools.context_queries.Topology import create_missing_topologies @@ -27,47 +25,54 @@ ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) INTERDOMAIN_TOPOLOGY_ID = TopologyId(**json_topology_id(INTERDOMAIN_TOPOLOGY_NAME, context_id=ADMIN_CONTEXT_ID)) -class DLTRecorder: +class DLTRecorder(threading.Thread): def __init__(self) -> None: - self.terminate_event = asyncio.Event() + super().__init__(daemon=True) + self.terminate = threading.Event() self.context_client = ContextClient() self.context_event_collector = EventsCollector(self.context_client) self.topology_cache: Dict[str, TopologyId] = {} - async def stop(self): - self.terminate_event.set() - - async def start(self) -> None: - await self.run() + def stop(self): + self.terminate.set() - async def run(self) -> None: - await self.context_client.connect() + def run(self) -> None: + self.context_client.connect() create_context(self.context_client, DEFAULT_CONTEXT_NAME) self.create_topologies() - await self.context_event_collector.start() + self.context_event_collector.start() - while not self.terminate_event.is_set(): - event = await self.context_event_collector.get_event(timeout=0.1) + while not self.terminate.is_set(): + event = self.context_event_collector.get_event(timeout=0.1) if event is None: continue LOGGER.info('Processing Event({:s})...'.format(grpc_message_to_json_string(event))) - await self.update_record(event) + self.update_record(event) - await self.context_event_collector.stop() - await self.context_client.close() + self.context_event_collector.stop() + self.context_client.close() def create_topologies(self): topology_uuids = [DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME] create_missing_topologies(self.context_client, ADMIN_CONTEXT_ID, topology_uuids) - async def get_dlt_connector_client(self) -> Optional[DltConnectorClient]: + def get_dlt_connector_client(self) -> Optional[DltConnectorClient]: # Always enable DLT for testing dlt_connector_client = DltConnectorClient() - await dlt_connector_client.connect() + dlt_connector_client.connect() return dlt_connector_client - - async def update_record(self, event: EventTypes) -> None: - dlt_connector_client = await self.get_dlt_connector_client() + # env_vars = find_environment_variables([ + # get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_HOST), + # get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + # ]) + # if len(env_vars) == 2: + # dlt_connector_client = DltConnectorClient() + # dlt_connector_client.connect() + # return dlt_connector_client + # return None + + def update_record(self, event: EventTypes) -> None: + dlt_connector_client = self.get_dlt_connector_client() dlt_record_sender = DltRecordSender(self.context_client, dlt_connector_client) if isinstance(event, ContextEvent): @@ -76,34 +81,34 @@ class DLTRecorder: elif isinstance(event, TopologyEvent): LOGGER.debug('Processing TopologyEvent({:s})'.format(grpc_message_to_json_string(event))) - await self.process_topology_event(event, dlt_record_sender) + self.process_topology_event(event, dlt_record_sender) elif isinstance(event, DeviceEvent): LOGGER.debug('Processing DeviceEvent({:s})'.format(grpc_message_to_json_string(event))) - await self.process_device_event(event, dlt_record_sender) + self.process_device_event(event, dlt_record_sender) elif isinstance(event, LinkEvent): LOGGER.debug('Processing LinkEvent({:s})'.format(grpc_message_to_json_string(event))) - await self.process_link_event(event, dlt_record_sender) + self.process_link_event(event, dlt_record_sender) else: LOGGER.warning('Unsupported Event({:s})'.format(grpc_message_to_json_string(event))) - await dlt_record_sender.commit() + dlt_record_sender.commit() if dlt_connector_client is not None: - await dlt_connector_client.close() + dlt_connector_client.close() - async def process_topology_event(self, event: TopologyEvent, dlt_record_sender: DltRecordSender) -> None: + def process_topology_event(self, event: TopologyEvent, dlt_record_sender: DltRecordSender) -> None: topology_id = event.topology_id topology_uuid = topology_id.topology_uuid.uuid context_id = topology_id.context_id context_uuid = context_id.context_uuid.uuid topology_uuids = {DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME} - context = await self.context_client.GetContext(context_id) + context = self.context_client.GetContext(context_id) context_name = context.name - topology_details = await self.context_client.GetTopologyDetails(topology_id) + topology_details = self.context_client.GetTopologyDetails(topology_id) topology_name = topology_details.name self.topology_cache[topology_uuid] = topology_id @@ -124,26 +129,26 @@ class DLTRecorder: args = context_uuid, context_name, topology_uuid, topology_name, grpc_message_to_json_string(event) LOGGER.warning(MSG.format(*args)) - async def find_topology_for_device(self, device_id: DeviceId) -> Optional[TopologyId]: + def find_topology_for_device(self, device_id: DeviceId) -> Optional[TopologyId]: for topology_uuid, topology_id in self.topology_cache.items(): - details = await self.context_client.GetTopologyDetails(topology_id) + details = self.context_client.GetTopologyDetails(topology_id) for device in details.devices: if device.device_id == device_id: return topology_id return None - async def find_topology_for_link(self, link_id: LinkId) -> Optional[TopologyId]: + def find_topology_for_link(self, link_id: LinkId) -> Optional[TopologyId]: for topology_uuid, topology_id in self.topology_cache.items(): - details = await self.context_client.GetTopologyDetails(topology_id) + details = self.context_client.GetTopologyDetails(topology_id) for link in details.links: if link.link_id == link_id: return topology_id return None - async def process_device_event(self, event: DeviceEvent, dlt_record_sender: DltRecordSender) -> None: + def process_device_event(self, event: DeviceEvent, dlt_record_sender: DltRecordSender) -> None: device_id = event.device_id - device = await self.context_client.GetDevice(device_id) - topology_id = await self.find_topology_for_device(device_id) + device = self.context_client.GetDevice(device_id) + topology_id = self.find_topology_for_device(device_id) if topology_id: LOGGER.debug('DEVICE_INFO({:s}), DEVICE_ID ({:s})'.format(str(device.device_id.device_uuid.uuid), grpc_message_to_json_string(device_id))) @@ -151,10 +156,10 @@ class DLTRecorder: else: LOGGER.warning(f"Topology not found for device {device_id.device_uuid.uuid}") - async def process_link_event(self, event: LinkEvent, dlt_record_sender: DltRecordSender) -> None: + def process_link_event(self, event: LinkEvent, dlt_record_sender: DltRecordSender) -> None: link_id = event.link_id - link = await self.context_client.GetLink(link_id) - topology_id = await self.find_topology_for_link(link_id) + link = self.context_client.GetLink(link_id) + topology_id = self.find_topology_for_link(link_id) if topology_id: dlt_record_sender.add_link(topology_id, link) else: -- GitLab From 34f8bb97487572bb01143ac022fe9d477e007bde Mon Sep 17 00:00:00 2001 From: diazjj Date: Wed, 31 Jul 2024 16:42:57 +0200 Subject: [PATCH 67/94] Async refactoring of the code. Initial --- src/common/method_wrappers/Decorator.py | 46 ++++++++ .../tools/service/GenericGrpcServiceAsync.py | 72 ++++++++++++ .../connector/client/DltConnectorClient.py | 70 +++++++---- .../client/DltConnectorClientSync.py | 111 ++++++++++++++++++ .../connector/client/DltEventsCollector.py | 4 +- src/dlt/connector/client/DltGatewayClient.py | 57 ++++++--- .../connector/client/DltGatewayClientEvent.py | 57 +++++++++ .../connector/service/DltConnectorService.py | 9 +- .../DltConnectorServiceServicerImpl.py | 96 +++++++++------ src/dlt/connector/service/__main__.py | 30 +++-- .../event_dispatcher/DltEventDispatcher.py | 8 +- .../topology_abstractor/DltRecordSender.py | 85 ++++++++------ .../topology_abstractor/DltRecorder.py | 86 +++++++++----- 13 files changed, 565 insertions(+), 166 deletions(-) create mode 100644 src/common/tools/service/GenericGrpcServiceAsync.py create mode 100644 src/dlt/connector/client/DltConnectorClientSync.py create mode 100644 src/dlt/connector/client/DltGatewayClientEvent.py diff --git a/src/common/method_wrappers/Decorator.py b/src/common/method_wrappers/Decorator.py index 71b3999bf..bfda31ec9 100644 --- a/src/common/method_wrappers/Decorator.py +++ b/src/common/method_wrappers/Decorator.py @@ -12,6 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import grpc, json, logging, threading from enum import Enum from prettytable import PrettyTable @@ -235,3 +249,35 @@ def safe_and_metered_rpc_method(metrics_pool : MetricsPool, logger : logging.Log grpc_context.abort(grpc.StatusCode.INTERNAL, str(e)) return inner_wrapper return outer_wrapper + +def safe_and_metered_rpc_method_async(metrics_pool: MetricsPool, logger: logging.Logger): + def outer_wrapper(func): + method_name = func.__name__ + metrics = metrics_pool.get_metrics(method_name) + histogram_duration, counter_started, counter_completed, counter_failed = metrics + + async def inner_wrapper(self, request, grpc_context: grpc.aio.ServicerContext): + counter_started.inc() + try: + logger.debug('{:s} request: {:s}'.format(method_name, grpc_message_to_json_string(request))) + reply = await func(self, request, grpc_context) + logger.debug('{:s} reply: {:s}'.format(method_name, grpc_message_to_json_string(reply))) + counter_completed.inc() + return reply + except ServiceException as e: # pragma: no cover (ServiceException not thrown) + if e.code not in [grpc.StatusCode.NOT_FOUND, grpc.StatusCode.ALREADY_EXISTS]: + # Assume not found or already exists is just a condition, not an error + logger.exception('{:s} exception'.format(method_name)) + counter_failed.inc() + else: + counter_completed.inc() + await grpc_context.abort(e.code, e.details) + except Exception as e: # pragma: no cover, pylint: disable=broad-except + logger.exception('{:s} exception'.format(method_name)) + counter_failed.inc() + await grpc_context.abort(grpc.StatusCode.INTERNAL, str(e)) + + return inner_wrapper + + return outer_wrapper + diff --git a/src/common/tools/service/GenericGrpcServiceAsync.py b/src/common/tools/service/GenericGrpcServiceAsync.py new file mode 100644 index 000000000..488d86177 --- /dev/null +++ b/src/common/tools/service/GenericGrpcServiceAsync.py @@ -0,0 +1,72 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Optional, Union +import grpc +import logging +from concurrent import futures +from grpc_health.v1.health import HealthServicer, OVERALL_HEALTH +from grpc_health.v1.health_pb2 import HealthCheckResponse +from grpc_health.v1.health_pb2_grpc import add_HealthServicer_to_server +from common.Settings import get_grpc_bind_address, get_grpc_grace_period, get_grpc_max_workers + +class GenericGrpcServiceAsync: + def __init__( + self, bind_port: Union[str, int], bind_address: Optional[str] = None, max_workers: Optional[int] = None, + grace_period: Optional[int] = None, enable_health_servicer: bool = True, cls_name: str = __name__ + ) -> None: + self.logger = logging.getLogger(cls_name) + self.bind_port = bind_port + self.bind_address = get_grpc_bind_address() if bind_address is None else bind_address + self.max_workers = get_grpc_max_workers() if max_workers is None else max_workers + self.grace_period = get_grpc_grace_period() if grace_period is None else grace_period + self.enable_health_servicer = enable_health_servicer + self.endpoint = None + self.health_servicer = None + self.pool = None + self.server = None + + async def install_servicers(self): + pass + + async def start(self): + self.endpoint = '{:s}:{:s}'.format(str(self.bind_address), str(self.bind_port)) + self.logger.info('Starting Service (tentative endpoint: {:s}, max_workers: {:s})...'.format( + str(self.endpoint), str(self.max_workers))) + + self.pool = futures.ThreadPoolExecutor(max_workers=self.max_workers) + self.server = grpc.aio.server(self.pool) + + await self.install_servicers() # Ensure this is awaited + + if self.enable_health_servicer: + self.health_servicer = HealthServicer( + experimental_non_blocking=True, experimental_thread_pool=futures.ThreadPoolExecutor(max_workers=1)) + add_HealthServicer_to_server(self.health_servicer, self.server) + + self.bind_port = self.server.add_insecure_port(self.endpoint) + self.endpoint = '{:s}:{:s}'.format(str(self.bind_address), str(self.bind_port)) + self.logger.info('Listening on {:s}...'.format(str(self.endpoint))) + await self.server.start() + if self.enable_health_servicer: + self.health_servicer.set(OVERALL_HEALTH, HealthCheckResponse.SERVING) + + self.logger.debug('Service started') + + async def stop(self): + self.logger.debug('Stopping service (grace period {:s} seconds)...'.format(str(self.grace_period))) + if self.enable_health_servicer: + self.health_servicer.enter_graceful_shutdown() + await self.server.stop(self.grace_period) + self.logger.debug('Service stopped') diff --git a/src/dlt/connector/client/DltConnectorClient.py b/src/dlt/connector/client/DltConnectorClient.py index e383217d8..a2224dd32 100644 --- a/src/dlt/connector/client/DltConnectorClient.py +++ b/src/dlt/connector/client/DltConnectorClient.py @@ -12,7 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc, logging +# DltConnectorClient.py + +import grpc +import logging +import asyncio + from common.Constants import ServiceNameEnum from common.Settings import get_service_host, get_service_port_grpc from common.proto.context_pb2 import Empty, TopologyId @@ -35,77 +40,90 @@ class DltConnectorClient: LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) self.channel = None self.stub = None - self.connect() - LOGGER.debug('Channel created') + #self.connect() + #LOGGER.debug('Channel created') - def connect(self): - self.channel = grpc.insecure_channel(self.endpoint) + async def connect(self): + self.channel = grpc.aio.insecure_channel(self.endpoint) self.stub = DltConnectorServiceStub(self.channel) + LOGGER.debug('Channel created') - def close(self): - if self.channel is not None: self.channel.close() + async def close(self): + if self.channel is not None: + await self.channel.close() self.channel = None self.stub = None @RETRY_DECORATOR - def RecordAll(self, request : TopologyId) -> Empty: + async def RecordAll(self, request: TopologyId) -> Empty: LOGGER.debug('RecordAll request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAll(request) + response = await self.stub.RecordAll(request) LOGGER.debug('RecordAll result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordAllDevices(self, request : TopologyId) -> Empty: + async def RecordAllDevices(self, request: TopologyId) -> Empty: LOGGER.debug('RecordAllDevices request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAllDevices(request) + response = await self.stub.RecordAllDevices(request) LOGGER.debug('RecordAllDevices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordDevice(self, request : DltDeviceId) -> Empty: - LOGGER.debug('RecordDevice request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordDevice(request) + # async def RecordDevice(self, request: DltDeviceId) -> Empty: + # LOGGER.debug('RECORD_DEVICE request received: {:s}'.format(grpc_message_to_json_string(request))) + + # Simulate some asynchronous processing delay + # await asyncio.sleep(2) # Simulates processing time + + # Create a dummy response (Empty message) + # response = Empty() + + # LOGGER.debug('RECORD_DEVICE processing complete for request: {:s}'.format(grpc_message_to_json_string(request))) + # return response + async def RecordDevice(self, request: DltDeviceId) -> Empty: + LOGGER.debug('RECORD_DEVICE request: {:s}'.format(grpc_message_to_json_string(request))) + response = await self.stub.RecordDevice(request) LOGGER.debug('RecordDevice result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordAllLinks(self, request : TopologyId) -> Empty: + async def RecordAllLinks(self, request: TopologyId) -> Empty: LOGGER.debug('RecordAllLinks request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAllLinks(request) + response = await self.stub.RecordAllLinks(request) LOGGER.debug('RecordAllLinks result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordLink(self, request : DltLinkId) -> Empty: + async def RecordLink(self, request: DltLinkId) -> Empty: LOGGER.debug('RecordLink request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordLink(request) + response = await self.stub.RecordLink(request) LOGGER.debug('RecordLink result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordAllServices(self, request : TopologyId) -> Empty: + async def RecordAllServices(self, request: TopologyId) -> Empty: LOGGER.debug('RecordAllServices request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAllServices(request) + response = await self.stub.RecordAllServices(request) LOGGER.debug('RecordAllServices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordService(self, request : DltServiceId) -> Empty: + async def RecordService(self, request: DltServiceId) -> Empty: LOGGER.debug('RecordService request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordService(request) + response = await self.stub.RecordService(request) LOGGER.debug('RecordService result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordAllSlices(self, request : TopologyId) -> Empty: + async def RecordAllSlices(self, request: TopologyId) -> Empty: LOGGER.debug('RecordAllSlices request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAllSlices(request) + response = await self.stub.RecordAllSlices(request) LOGGER.debug('RecordAllSlices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordSlice(self, request : DltSliceId) -> Empty: + async def RecordSlice(self, request: DltSliceId) -> Empty: LOGGER.debug('RecordSlice request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordSlice(request) + response = await self.stub.RecordSlice(request) LOGGER.debug('RecordSlice result: {:s}'.format(grpc_message_to_json_string(response))) return response diff --git a/src/dlt/connector/client/DltConnectorClientSync.py b/src/dlt/connector/client/DltConnectorClientSync.py new file mode 100644 index 000000000..a633e89bd --- /dev/null +++ b/src/dlt/connector/client/DltConnectorClientSync.py @@ -0,0 +1,111 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import grpc, logging +from common.Constants import ServiceNameEnum +from common.Settings import get_service_host, get_service_port_grpc +from common.proto.context_pb2 import Empty, TopologyId +from common.proto.dlt_connector_pb2 import DltDeviceId, DltLinkId, DltServiceId, DltSliceId +from common.proto.dlt_connector_pb2_grpc import DltConnectorServiceStub +from common.tools.client.RetryDecorator import retry, delay_exponential +from common.tools.grpc.Tools import grpc_message_to_json_string + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +MAX_RETRIES = 15 +DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) +RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') + +class DltConnectorClientSync: + def __init__(self, host=None, port=None): + if not host: host = get_service_host(ServiceNameEnum.DLT) + if not port: port = get_service_port_grpc(ServiceNameEnum.DLT) + self.endpoint = '{:s}:{:s}'.format(str(host), str(port)) + LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) + self.channel = None + self.stub = None + self.connect() + LOGGER.debug('Channel created') + + def connect(self): + self.channel = grpc.insecure_channel(self.endpoint) + self.stub = DltConnectorServiceStub(self.channel) + + def close(self): + if self.channel is not None: self.channel.close() + self.channel = None + self.stub = None + + @RETRY_DECORATOR + def RecordAll(self, request : TopologyId) -> Empty: + LOGGER.debug('RecordAll request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.RecordAll(request) + LOGGER.debug('RecordAll result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def RecordAllDevices(self, request : TopologyId) -> Empty: + LOGGER.debug('RecordAllDevices request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.RecordAllDevices(request) + LOGGER.debug('RecordAllDevices result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def RecordDevice(self, request : DltDeviceId) -> Empty: + LOGGER.debug('RecordDevice request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.RecordDevice(request) + LOGGER.debug('RecordDevice result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def RecordAllLinks(self, request : TopologyId) -> Empty: + LOGGER.debug('RecordAllLinks request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.RecordAllLinks(request) + LOGGER.debug('RecordAllLinks result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def RecordLink(self, request : DltLinkId) -> Empty: + LOGGER.debug('RecordLink request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.RecordLink(request) + LOGGER.debug('RecordLink result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def RecordAllServices(self, request : TopologyId) -> Empty: + LOGGER.debug('RecordAllServices request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.RecordAllServices(request) + LOGGER.debug('RecordAllServices result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def RecordService(self, request : DltServiceId) -> Empty: + LOGGER.debug('RecordService request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.RecordService(request) + LOGGER.debug('RecordService result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def RecordAllSlices(self, request : TopologyId) -> Empty: + LOGGER.debug('RecordAllSlices request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.RecordAllSlices(request) + LOGGER.debug('RecordAllSlices result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def RecordSlice(self, request : DltSliceId) -> Empty: + LOGGER.debug('RecordSlice request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.RecordSlice(request) + LOGGER.debug('RecordSlice result: {:s}'.format(grpc_message_to_json_string(response))) + return response diff --git a/src/dlt/connector/client/DltEventsCollector.py b/src/dlt/connector/client/DltEventsCollector.py index e59784a4d..2e38d0445 100644 --- a/src/dlt/connector/client/DltEventsCollector.py +++ b/src/dlt/connector/client/DltEventsCollector.py @@ -16,7 +16,7 @@ from typing import Callable, Optional import grpc, logging, queue, threading, time from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordSubscription from common.tools.grpc.Tools import grpc_message_to_json_string -from dlt.connector.client.DltGatewayClient import DltGatewayClient +from dlt.connector.client.DltGatewayClientEvent import DltGatewayClientEvent LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) @@ -31,7 +31,7 @@ LOGGER.setLevel(logging.DEBUG) class DltEventsCollector(threading.Thread): def __init__( - self, dltgateway_client : DltGatewayClient, + self, dltgateway_client : DltGatewayClientEvent, log_events_received : bool = False, event_handler : Optional[Callable[[DltRecordEvent], Optional[DltRecordEvent]]] = None, ) -> None: diff --git a/src/dlt/connector/client/DltGatewayClient.py b/src/dlt/connector/client/DltGatewayClient.py index cde278517..2690dfb66 100644 --- a/src/dlt/connector/client/DltGatewayClient.py +++ b/src/dlt/connector/client/DltGatewayClient.py @@ -12,8 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Iterator -import grpc, logging + + +import grpc +import logging +import asyncio +from typing import Iterator, List from common.proto.context_pb2 import Empty, TeraFlowController from common.proto.dlt_gateway_pb2 import ( DltPeerStatus, DltPeerStatusList, DltRecord, DltRecordEvent, DltRecordId, DltRecordStatus, DltRecordSubscription) @@ -36,29 +40,50 @@ class DltGatewayClient: LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) self.channel = None self.stub = None - self.connect() - LOGGER.debug('Channel created') + #self.connect() + self.message_queue: List[DltRecord] = [] + #LOGGER.debug('Channel created') + - def connect(self): - self.channel = grpc.insecure_channel(self.endpoint) + async def connect(self): + self.channel = grpc.aio.insecure_channel(self.endpoint) self.stub = DltGatewayServiceStub(self.channel) + LOGGER.debug('Channel created') - def close(self): - if self.channel is not None: self.channel.close() + async def close(self): + if self.channel is not None: + await self.channel.close() self.channel = None self.stub = None + # async def dummy_process(self, request: DltRecord): + # # Simulate processing delay + # await asyncio.sleep(2) + # return DltRecordStatus(status="DLTRECORDSTATUS_SUCCEEDED", record_id=request.record_id) + + @RETRY_DECORATOR - def RecordToDlt(self, request : DltRecord) -> DltRecordStatus: + async def RecordToDlt(self, request : DltRecord) -> DltRecordStatus: LOGGER.debug('RecordToDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordToDlt(request) + response = await self.stub.RecordToDlt(request) LOGGER.debug('RecordToDlt result: {:s}'.format(grpc_message_to_json_string(response))) return response + + # @RETRY_DECORATOR + # async def RecordToDlt(self, request: DltRecord) -> DltRecordStatus: + # self.message_queue.append(request) + # LOGGER.debug(f'RecordToDlt request: {grpc_message_to_json_string(request)}') + # LOGGER.debug(f'Queue length before processing: {len(self.message_queue)}') + # response = await self.dummy_process(request) + # LOGGER.debug(f'RecordToDlt result: {grpc_message_to_json_string(response)}') + # self.message_queue.remove(request) + # LOGGER.debug(f'Queue length after processing: {len(self.message_queue)}') + # return response @RETRY_DECORATOR - def GetFromDlt(self, request : DltRecordId) -> DltRecord: + async def GetFromDlt(self, request : DltRecordId) -> DltRecord: LOGGER.debug('GetFromDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.GetFromDlt(request) + response = await self.stub.GetFromDlt(request) LOGGER.debug('GetFromDlt result: {:s}'.format(grpc_message_to_json_string(response))) return response @@ -70,15 +95,15 @@ class DltGatewayClient: return response @RETRY_DECORATOR - def GetDltStatus(self, request : TeraFlowController) -> DltPeerStatus: + async def GetDltStatus(self, request : TeraFlowController) -> DltPeerStatus: LOGGER.debug('GetDltStatus request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.GetDltStatus(request) + response = await self.stub.GetDltStatus(request) LOGGER.debug('GetDltStatus result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def GetDltPeers(self, request : Empty) -> DltPeerStatusList: + async def GetDltPeers(self, request : Empty) -> DltPeerStatusList: LOGGER.debug('GetDltPeers request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.GetDltPeers(request) + response = await self.stub.GetDltPeers(request) LOGGER.debug('GetDltPeers result: {:s}'.format(grpc_message_to_json_string(response))) return response diff --git a/src/dlt/connector/client/DltGatewayClientEvent.py b/src/dlt/connector/client/DltGatewayClientEvent.py new file mode 100644 index 000000000..6cbaf1a27 --- /dev/null +++ b/src/dlt/connector/client/DltGatewayClientEvent.py @@ -0,0 +1,57 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import grpc +import logging +from typing import Iterator +from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordSubscription +from common.proto.dlt_gateway_pb2_grpc import DltGatewayServiceStub +from common.tools.client.RetryDecorator import retry, delay_exponential +from common.tools.grpc.Tools import grpc_message_to_json_string +from dlt.connector.Config import DLT_GATEWAY_HOST, DLT_GATEWAY_PORT + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +MAX_RETRIES = 15 +DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) +RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') + +class DltGatewayClientEvent: + def __init__(self, host=None, port=None): + if not host: host = DLT_GATEWAY_HOST + if not port: port = DLT_GATEWAY_PORT + self.endpoint = '{:s}:{:s}'.format(str(host), str(port)) + LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) + self.channel = None + self.stub = None + self.connect() + LOGGER.debug('Channel created') + + def connect(self): + self.channel = grpc.insecure_channel(self.endpoint) + self.stub = DltGatewayServiceStub(self.channel) + + def close(self): + if self.channel is not None: + self.channel.close() + self.channel = None + self.stub = None + + @RETRY_DECORATOR + def SubscribeToDlt(self, request: DltRecordSubscription) -> Iterator[DltRecordEvent]: + LOGGER.debug('SubscribeToDlt request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.SubscribeToDlt(request) + LOGGER.debug('SubscribeToDlt result: {:s}'.format(grpc_message_to_json_string(response))) + return response diff --git a/src/dlt/connector/service/DltConnectorService.py b/src/dlt/connector/service/DltConnectorService.py index 7e99cb8f8..601d3e70d 100644 --- a/src/dlt/connector/service/DltConnectorService.py +++ b/src/dlt/connector/service/DltConnectorService.py @@ -14,15 +14,16 @@ from common.Constants import ServiceNameEnum from common.Settings import get_service_port_grpc -from common.tools.service.GenericGrpcService import GenericGrpcService +from common.tools.service.GenericGrpcServiceAsync import GenericGrpcServiceAsync from common.proto.dlt_connector_pb2_grpc import add_DltConnectorServiceServicer_to_server from .DltConnectorServiceServicerImpl import DltConnectorServiceServicerImpl -class DltConnectorService(GenericGrpcService): +class DltConnectorService(GenericGrpcServiceAsync): def __init__(self, cls_name: str = __name__) -> None: port = get_service_port_grpc(ServiceNameEnum.DLT) super().__init__(port, cls_name=cls_name) self.dltconnector_servicer = DltConnectorServiceServicerImpl() - def install_servicers(self): - add_DltConnectorServiceServicer_to_server(self.dltconnector_servicer, self.server) + async def install_servicers(self): + await self.dltconnector_servicer.initialize() + add_DltConnectorServiceServicer_to_server(self.dltconnector_servicer, self.server) \ No newline at end of file diff --git a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py index c05d46b48..46c58e063 100644 --- a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py +++ b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py @@ -12,9 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc, logging +import grpc +import asyncio +from grpc.aio import ServicerContext +import logging from typing import Optional -from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method +from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method_async from common.proto.context_pb2 import Empty, TopologyId from common.proto.dlt_connector_pb2 import DltDeviceId, DltLinkId, DltServiceId, DltSliceId from common.proto.dlt_connector_pb2_grpc import DltConnectorServiceServicer @@ -32,94 +35,109 @@ METRICS_POOL = MetricsPool('DltConnector', 'RPC') class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): def __init__(self): LOGGER.debug('Creating Servicer...') + self.dltgateway_client = DltGatewayClient() LOGGER.debug('Servicer Created') - @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordAll(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + async def initialize(self): + await self.dltgateway_client.connect() + + @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) + async def RecordAll(self, request: TopologyId, context: ServicerContext) -> Empty: return Empty() - @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordAllDevices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) + async def RecordAllDevices(self, request: TopologyId, context: ServicerContext) -> Empty: return Empty() - @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordDevice(self, request : DltDeviceId, context : grpc.ServicerContext) -> Empty: + @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) + # async def RecordDevice(self, request: DltDeviceId, context: ServicerContext) -> Empty: + # LOGGER.debug('Received RecordDevice request: {:s}'.format(grpc_message_to_json_string(request))) + # try: + # if not request.delete: + # LOGGER.debug('Processing RecordDevice request: {:s}'.format(grpc_message_to_json_string(request))) + # # Perform any dummy operation or simply log the request + # LOGGER.debug('Processed RecordDevice request: {:s}'.format(grpc_message_to_json_string(request))) + # except Exception as e: + # LOGGER.error(f"Error processing RecordDevice: {e}") + # return Empty() + async def RecordDevice(self, request: DltDeviceId, context: ServicerContext) -> Empty: data_json = None - if not request.delete: + LOGGER.debug('RECORD_DEVICE = {:s}'.format(grpc_message_to_json_string(request))) + if not request.delete: context_client = ContextClient() device = context_client.GetDevice(request.device_id) data_json = grpc_message_to_json_string(device) - self._record_entity( + await self._record_entity( request.topology_id.topology_uuid.uuid, DltRecordTypeEnum.DLTRECORDTYPE_DEVICE, request.device_id.device_uuid.uuid, request.delete, data_json) return Empty() - @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordAllLinks(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) + async def RecordAllLinks(self, request: TopologyId, context: ServicerContext) -> Empty: return Empty() - @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordLink(self, request : DltLinkId, context : grpc.ServicerContext) -> Empty: + @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) + async def RecordLink(self, request: DltLinkId, context: ServicerContext) -> Empty: data_json = None + LOGGER.debug('RECORD_LINK = {:s}'.format(grpc_message_to_json_string(request))) + if not request.delete: context_client = ContextClient() link = context_client.GetLink(request.link_id) data_json = grpc_message_to_json_string(link) - self._record_entity( + await self._record_entity( request.topology_id.topology_uuid.uuid, DltRecordTypeEnum.DLTRECORDTYPE_LINK, request.link_id.link_uuid.uuid, request.delete, data_json) return Empty() - @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordAllServices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) + async def RecordAllServices(self, request: TopologyId, context: ServicerContext) -> Empty: return Empty() - @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordService(self, request : DltServiceId, context : grpc.ServicerContext) -> Empty: + @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) + async def RecordService(self, request: DltServiceId, context: ServicerContext) -> Empty: data_json = None if not request.delete: context_client = ContextClient() service = context_client.GetService(request.service_id) data_json = grpc_message_to_json_string(service) - self._record_entity( + await self._record_entity( request.topology_id.topology_uuid.uuid, DltRecordTypeEnum.DLTRECORDTYPE_SERVICE, request.service_id.service_uuid.uuid, request.delete, data_json) return Empty() - @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordAllSlices(self, request : TopologyId, context : grpc.ServicerContext) -> Empty: + @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) + async def RecordAllSlices(self, request: TopologyId, context: ServicerContext) -> Empty: return Empty() - @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) - def RecordSlice(self, request : DltSliceId, context : grpc.ServicerContext) -> Empty: + @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) + async def RecordSlice(self, request: DltSliceId, context: ServicerContext) -> Empty: data_json = None if not request.delete: context_client = ContextClient() slice_ = context_client.GetSlice(request.slice_id) data_json = grpc_message_to_json_string(slice_) - self._record_entity( + await self._record_entity( request.topology_id.topology_uuid.uuid, DltRecordTypeEnum.DLTRECORDTYPE_SLICE, request.slice_id.slice_uuid.uuid, request.delete, data_json) return Empty() - def _record_entity( - self, dlt_domain_uuid : str, dlt_record_type : DltRecordTypeEnum, dlt_record_uuid : str, delete : bool, - data_json : Optional[str] = None + async def _record_entity( + self, dlt_domain_uuid: str, dlt_record_type: DltRecordTypeEnum, dlt_record_uuid: str, delete: bool, + data_json: Optional[str] = None ) -> None: - dltgateway_client = DltGatewayClient() - dlt_record_id = DltRecordId() - dlt_record_id.domain_uuid.uuid = dlt_domain_uuid # pylint: disable=no-member - dlt_record_id.type = dlt_record_type - dlt_record_id.record_uuid.uuid = dlt_record_uuid # pylint: disable=no-member + dlt_record_id.domain_uuid.uuid = dlt_domain_uuid # pylint: disable=no-member + dlt_record_id.type = dlt_record_type + dlt_record_id.record_uuid.uuid = dlt_record_uuid # pylint: disable=no-member str_dlt_record_id = grpc_message_to_json_string(dlt_record_id) LOGGER.debug('[_record_entity] sent dlt_record_id = {:s}'.format(str_dlt_record_id)) - dlt_record = dltgateway_client.GetFromDlt(dlt_record_id) + dlt_record = await self.dltgateway_client.GetFromDlt(dlt_record_id) str_dlt_record = grpc_message_to_json_string(dlt_record) LOGGER.debug('[_record_entity] recv dlt_record = {:s}'.format(str_dlt_record)) @@ -127,22 +145,24 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): LOGGER.debug('[_record_entity] exists = {:s}'.format(str(exists))) dlt_record = DltRecord() - dlt_record.record_id.CopyFrom(dlt_record_id) # pylint: disable=no-member + dlt_record.record_id.CopyFrom(dlt_record_id) # pylint: disable=no-member if delete and exists: dlt_record.operation = DltRecordOperationEnum.DLTRECORDOPERATION_DELETE elif not delete and exists: dlt_record.operation = DltRecordOperationEnum.DLTRECORDOPERATION_UPDATE - if data_json is None: raise Exception('data_json must be provided when updating') + if data_json is None: + raise Exception('data_json must be provided when updating') dlt_record.data_json = data_json elif not delete and not exists: dlt_record.operation = DltRecordOperationEnum.DLTRECORDOPERATION_ADD - if data_json is None: raise Exception('data_json must be provided when adding') + if data_json is None: + raise Exception('data_json must be provided when adding') dlt_record.data_json = data_json else: return str_dlt_record = grpc_message_to_json_string(dlt_record) LOGGER.debug('[_record_entity] sent dlt_record = {:s}'.format(str_dlt_record)) - dlt_record_status = dltgateway_client.RecordToDlt(dlt_record) + dlt_record_status = await self.dltgateway_client.RecordToDlt(dlt_record) str_dlt_record_status = grpc_message_to_json_string(dlt_record_status) LOGGER.debug('[_record_entity] recv dlt_record_status = {:s}'.format(str_dlt_record_status)) diff --git a/src/dlt/connector/service/__main__.py b/src/dlt/connector/service/__main__.py index b13f4257b..09f525ed4 100644 --- a/src/dlt/connector/service/__main__.py +++ b/src/dlt/connector/service/__main__.py @@ -12,7 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging, signal, sys, threading + +import logging +import signal +import sys +import threading +import asyncio from prometheus_client import start_http_server from common.Constants import ServiceNameEnum from common.Settings import ( @@ -22,25 +27,25 @@ from .event_dispatcher.DltEventDispatcher import DltEventDispatcher from .DltConnectorService import DltConnectorService terminate = threading.Event() -LOGGER : logging.Logger = None +LOGGER: logging.Logger = None -def signal_handler(signal, frame): # pylint: disable=redefined-outer-name +def signal_handler(signal, frame): LOGGER.warning('Terminate signal received') terminate.set() -def main(): - global LOGGER # pylint: disable=global-statement +async def main(): + global LOGGER log_level = get_log_level() logging.basicConfig(level=log_level, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s") LOGGER = logging.getLogger(__name__) wait_for_environment_variables([ - get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_HOST ), + get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_HOST), get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), ]) - signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) LOGGER.info('Starting...') @@ -53,17 +58,16 @@ def main(): event_dispatcher = DltEventDispatcher() event_dispatcher.start() - # Context Event dispatcher - # Starting DLT connector service grpc_service = DltConnectorService() - grpc_service.start() + await grpc_service.start() # Wait for Ctrl+C or termination signal - while not terminate.wait(timeout=1.0): pass + while not terminate.wait(timeout=1.0): + await asyncio.sleep(1.0) LOGGER.info('Terminating...') - grpc_service.stop() + await grpc_service.stop() event_dispatcher.stop() event_dispatcher.join() @@ -71,4 +75,4 @@ def main(): return 0 if __name__ == '__main__': - sys.exit(main()) + asyncio.run(main()) diff --git a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py index 779bae9c1..29e8c096d 100644 --- a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py +++ b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py @@ -26,9 +26,9 @@ from common.tools.grpc.Tools import grpc_message_to_json_string from common.tools.object_factory.Context import json_context_id from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient -from dlt.connector.client.DltConnectorClient import DltConnectorClient +from dlt.connector.client.DltConnectorClientSync import DltConnectorClientSync from dlt.connector.client.DltEventsCollector import DltEventsCollector -from dlt.connector.client.DltGatewayClient import DltGatewayClient +from dlt.connector.client.DltGatewayClientEvent import DltGatewayClientEvent from interdomain.client.InterdomainClient import InterdomainClient LOGGER = logging.getLogger(__name__) @@ -40,8 +40,8 @@ ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) class Clients: def __init__(self) -> None: self.context_client = ContextClient() - self.dlt_connector_client = DltConnectorClient() - self.dlt_gateway_client = DltGatewayClient() + self.dlt_connector_client = DltConnectorClientSync() + self.dlt_gateway_client = DltGatewayClientEvent() self.interdomain_client = InterdomainClient() def close(self) -> None: diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index cfa51c928..d6d5ef0f6 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -12,91 +12,108 @@ # See the License for the specific language governing permissions and # limitations under the License. +# DltRecordSender.py + import logging +import asyncio + + from typing import Dict, List, Optional, Tuple from common.proto.context_pb2 import Device, Link, Service, Slice, TopologyId from common.proto.dlt_connector_pb2 import DltDeviceId, DltLinkId, DltServiceId, DltSliceId from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient from dlt.connector.client.DltConnectorClient import DltConnectorClient -from .Types import DltRecordTypes LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) class DltRecordSender: - def __init__(self, context_client : ContextClient, dlt_connector_client : Optional[DltConnectorClient]) -> None: + def __init__(self, context_client: ContextClient) -> None: self.context_client = context_client - self.dlt_connector_client = dlt_connector_client - self.dlt_record_uuids : List[str] = list() - self.dlt_record_uuid_to_data : Dict[str, Tuple[TopologyId, DltRecordTypes]] = dict() + LOGGER.debug('Creating Servicer...') + self.dlt_connector_client = DltConnectorClient() + LOGGER.debug('Servicer Created') + self.dlt_record_uuids: List[str] = list() + self.dlt_record_uuid_to_data: Dict[str, Tuple[TopologyId, object]] = dict() + + async def initialize(self): + await self.dlt_connector_client.connect() - def _add_record(self, record_uuid : str, data : Tuple[TopologyId, DltRecordTypes]) -> None: + def _add_record(self, record_uuid: str, data: Tuple[TopologyId, object]) -> None: if record_uuid in self.dlt_record_uuid_to_data: return self.dlt_record_uuid_to_data[record_uuid] = data self.dlt_record_uuids.append(record_uuid) - def add_device(self, topology_id : TopologyId, device : Device) -> None: + def add_device(self, topology_id: TopologyId, device: Device) -> None: topology_uuid = topology_id.topology_uuid.uuid device_uuid = device.device_id.device_uuid.uuid - record_uuid = '{:s}:device:{:s}'.format(topology_uuid, device_uuid) + record_uuid = f'{topology_uuid}:device:{device_uuid}' self._add_record(record_uuid, (topology_id, device)) - def add_link(self, topology_id : TopologyId, link : Link) -> None: + def add_link(self, topology_id: TopologyId, link: Link) -> None: topology_uuid = topology_id.topology_uuid.uuid link_uuid = link.link_id.link_uuid.uuid - record_uuid = '{:s}:link:{:s}'.format(topology_uuid, link_uuid) + record_uuid = f'{topology_uuid}:link:{link_uuid}' self._add_record(record_uuid, (topology_id, link)) - def add_service(self, topology_id : TopologyId, service : Service) -> None: + def add_service(self, topology_id: TopologyId, service: Service) -> None: topology_uuid = topology_id.topology_uuid.uuid context_uuid = service.service_id.context_id.context_uuid.uuid service_uuid = service.service_id.service_uuid.uuid - record_uuid = '{:s}:service:{:s}/{:s}'.format(topology_uuid, context_uuid, service_uuid) + record_uuid = f'{topology_uuid}:service:{context_uuid}/{service_uuid}' self._add_record(record_uuid, (topology_id, service)) - def add_slice(self, topology_id : TopologyId, slice_ : Slice) -> None: + def add_slice(self, topology_id: TopologyId, slice_: Slice) -> None: topology_uuid = topology_id.topology_uuid.uuid context_uuid = slice_.slice_id.context_id.context_uuid.uuid slice_uuid = slice_.slice_id.slice_uuid.uuid - record_uuid = '{:s}:slice:{:s}/{:s}'.format(topology_uuid, context_uuid, slice_uuid) + record_uuid = f'{topology_uuid}:slice:{context_uuid}/{slice_uuid}' self._add_record(record_uuid, (topology_id, slice_)) - def commit(self) -> None: + async def commit(self) -> None: + if not self.dlt_connector_client: + LOGGER.error('DLT Connector Client is None, cannot commit records.') + return + tasks = [] # List to hold all the async tasks + for dlt_record_uuid in self.dlt_record_uuids: - topology_id,dlt_record = self.dlt_record_uuid_to_data[dlt_record_uuid] + topology_id, dlt_record = self.dlt_record_uuid_to_data[dlt_record_uuid] if isinstance(dlt_record, Device): - #device_id = self.context_client.SetDevice(dlt_record) # This causes events to be triggered infinitely. device_id = dlt_record.device_id - #LOGGER.debug('DEVICE_ID: ({:s})'.format(grpc_message_to_json_string(device_id))) if self.dlt_connector_client is None: continue dlt_device_id = DltDeviceId() - dlt_device_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member - dlt_device_id.device_id.CopyFrom(device_id) # pylint: disable=no-member - self.dlt_connector_client.RecordDevice(dlt_device_id) + dlt_device_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member + dlt_device_id.device_id.CopyFrom(device_id) # pylint: disable=no-member + tasks.append(self.dlt_connector_client.RecordDevice(dlt_device_id)) +# await self.dlt_connector_client.RecordDevice(dlt_device_id) elif isinstance(dlt_record, Link): - #link_id = self.context_client.SetLink(dlt_record) link_id = dlt_record.link_id if self.dlt_connector_client is None: continue dlt_link_id = DltLinkId() - dlt_link_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member - dlt_link_id.link_id.CopyFrom(link_id) # pylint: disable=no-member - self.dlt_connector_client.RecordLink(dlt_link_id) + dlt_link_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member + dlt_link_id.link_id.CopyFrom(link_id) # pylint: disable=no-member + tasks.append(self.dlt_connector_client.RecordLink(dlt_link_id)) + #await self.dlt_connector_client.RecordLink(dlt_link_id) elif isinstance(dlt_record, Service): - #service_id = self.context_client.SetService(dlt_record) service_id = dlt_record.service_id if self.dlt_connector_client is None: continue dlt_service_id = DltServiceId() - dlt_service_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member - dlt_service_id.service_id.CopyFrom(service_id) # pylint: disable=no-member - self.dlt_connector_client.RecordService(dlt_service_id) + dlt_service_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member + dlt_service_id.service_id.CopyFrom(service_id) # pylint: disable=no-member + tasks.append(self.dlt_connector_client.RecordService(dlt_service_id)) + #await self.dlt_connector_client.RecordService(dlt_service_id) elif isinstance(dlt_record, Slice): - #slice_id = self.context_client.SetSlice(dlt_record) slice_id = dlt_record.slice_id if self.dlt_connector_client is None: continue dlt_slice_id = DltSliceId() - dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member - dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member - self.dlt_connector_client.RecordSlice(dlt_slice_id) + dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member + dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member + tasks.append(self.dlt_connector_client.RecordSlice(dlt_slice_id)) + #await self.dlt_connector_client.RecordSlice(dlt_slice_id) else: - LOGGER.error('Unsupported Record({:s})'.format(str(dlt_record))) + LOGGER.error(f'Unsupported Record({str(dlt_record)})') + + if tasks: + await asyncio.gather(*tasks) # Run all the tasks concurrently + diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 0e94159c4..c5660e43d 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -1,5 +1,20 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import logging import threading +import asyncio from typing import Dict, Optional from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum @@ -14,7 +29,6 @@ from common.tools.object_factory.Device import json_device_id from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient from context.client.EventsCollector import EventsCollector -from dlt.connector.client.DltConnectorClient import DltConnectorClient from .DltRecordSender import DltRecordSender from .Types import EventTypes @@ -37,43 +51,54 @@ class DLTRecorder(threading.Thread): self.terminate.set() def run(self) -> None: + asyncio.run(self._run()) + + async def _run(self) -> None: self.context_client.connect() create_context(self.context_client, DEFAULT_CONTEXT_NAME) - self.create_topologies() + #self.create_topologies() self.context_event_collector.start() + + tasks = [] + batch_timeout = 1 # Time in seconds to wait before processing whatever tasks are available while not self.terminate.is_set(): event = self.context_event_collector.get_event(timeout=0.1) if event is None: continue LOGGER.info('Processing Event({:s})...'.format(grpc_message_to_json_string(event))) - self.update_record(event) + task = asyncio.create_task(self.update_record(event)) + tasks.append(task) + LOGGER.debug('Task for event scheduled.') + # Limit the number of concurrent tasks + # If we have enough tasks or it's time to process them + if len(tasks) >= 10 or (tasks and len(tasks) > 0 and await asyncio.sleep(batch_timeout)): + try: + await asyncio.gather(*tasks) + except Exception as e: + LOGGER.error(f"Error while processing tasks: {e}") + finally: + tasks = [] # Clear the list after processing + await asyncio.gather(*tasks) + tasks = [] # Clear the list after processing + # Process any remaining tasks when stopping + if tasks: + try: + await asyncio.gather(*tasks) + except Exception as e: + LOGGER.error(f"Error while processing remaining tasks: {e}") self.context_event_collector.stop() self.context_client.close() - def create_topologies(self): - topology_uuids = [DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME] - create_missing_topologies(self.context_client, ADMIN_CONTEXT_ID, topology_uuids) - - def get_dlt_connector_client(self) -> Optional[DltConnectorClient]: - # Always enable DLT for testing - dlt_connector_client = DltConnectorClient() - dlt_connector_client.connect() - return dlt_connector_client - # env_vars = find_environment_variables([ - # get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_HOST), - # get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), - # ]) - # if len(env_vars) == 2: - # dlt_connector_client = DltConnectorClient() - # dlt_connector_client.connect() - # return dlt_connector_client - # return None - - def update_record(self, event: EventTypes) -> None: - dlt_connector_client = self.get_dlt_connector_client() - dlt_record_sender = DltRecordSender(self.context_client, dlt_connector_client) + #def create_topologies(self): + #topology_uuids = [DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME] + #create_missing_topologies(self.context_client, ADMIN_CONTEXT_ID, topology_uuids) + + async def update_record(self, event: EventTypes) -> None: + dlt_record_sender = DltRecordSender(self.context_client) + await dlt_record_sender.initialize() # Ensure DltRecordSender is initialized asynchronously + LOGGER.debug('STARTING processing event: {:s}'.format(grpc_message_to_json_string(event))) if isinstance(event, ContextEvent): LOGGER.debug('Processing ContextEvent({:s})'.format(grpc_message_to_json_string(event))) @@ -84,7 +109,7 @@ class DLTRecorder(threading.Thread): self.process_topology_event(event, dlt_record_sender) elif isinstance(event, DeviceEvent): - LOGGER.debug('Processing DeviceEvent({:s})'.format(grpc_message_to_json_string(event))) + LOGGER.debug('Processing DeviceEvent ASYNC({:s})'.format(grpc_message_to_json_string(event))) self.process_device_event(event, dlt_record_sender) elif isinstance(event, LinkEvent): @@ -94,9 +119,10 @@ class DLTRecorder(threading.Thread): else: LOGGER.warning('Unsupported Event({:s})'.format(grpc_message_to_json_string(event))) - dlt_record_sender.commit() - if dlt_connector_client is not None: - dlt_connector_client.close() + await dlt_record_sender.commit() + #await asyncio.sleep(2) # Simulates processing time + LOGGER.debug('Finished processing event: {:s}'.format(grpc_message_to_json_string(event))) + def process_topology_event(self, event: TopologyEvent, dlt_record_sender: DltRecordSender) -> None: topology_id = event.topology_id @@ -117,6 +143,7 @@ class DLTRecorder(threading.Thread): if ((context_uuid == DEFAULT_CONTEXT_NAME) or (context_name == DEFAULT_CONTEXT_NAME)) and \ (topology_uuid not in topology_uuids) and (topology_name not in topology_uuids): + LOGGER.debug('DEVICES({:s})'.format(grpc_message_to_json_string(topology_details.devices))) for device in topology_details.devices: LOGGER.debug('DEVICE_INFO_TOPO({:s})'.format(grpc_message_to_json_string(device))) dlt_record_sender.add_device(topology_id, device) @@ -164,3 +191,4 @@ class DLTRecorder(threading.Thread): dlt_record_sender.add_link(topology_id, link) else: LOGGER.warning(f"Topology not found for link {link_id.link_uuid.uuid}") + -- GitLab From 113a54ddefa6cf3ed0d2663b7f53a8e0d844633a Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Wed, 28 Aug 2024 12:30:13 +0200 Subject: [PATCH 68/94] Async implementation --- .../topology_abstractor/DltRecorder.py | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index c5660e43d..418a53612 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -15,6 +15,7 @@ import logging import threading import asyncio +import time from typing import Dict, Optional from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum @@ -47,6 +48,12 @@ class DLTRecorder(threading.Thread): self.context_event_collector = EventsCollector(self.context_client) self.topology_cache: Dict[str, TopologyId] = {} + # Queues for each event type + self.create_event_queue = asyncio.Queue() + self.update_event_queue = asyncio.Queue() + self.remove_event_queue = asyncio.Queue() + + def stop(self): self.terminate.set() @@ -61,27 +68,29 @@ class DLTRecorder(threading.Thread): tasks = [] batch_timeout = 1 # Time in seconds to wait before processing whatever tasks are available + last_task_time = time.time() while not self.terminate.is_set(): event = self.context_event_collector.get_event(timeout=0.1) - if event is None: - continue - LOGGER.info('Processing Event({:s})...'.format(grpc_message_to_json_string(event))) - task = asyncio.create_task(self.update_record(event)) - tasks.append(task) - LOGGER.debug('Task for event scheduled.') - # Limit the number of concurrent tasks - # If we have enough tasks or it's time to process them - if len(tasks) >= 10 or (tasks and len(tasks) > 0 and await asyncio.sleep(batch_timeout)): + if event: + LOGGER.info('Processing Event({:s})...'.format(grpc_message_to_json_string(event))) + task = asyncio.create_task(self.update_record(event)) + tasks.append(task) + LOGGER.debug('Task for event scheduled.') + + # Update the last task time since we've added a new task + last_task_time = time.time() + + # Check if it's time to process the tasks or if we have enough tasks + if tasks and (len(tasks) >= 10 or (time.time() - last_task_time >= batch_timeout)): try: await asyncio.gather(*tasks) except Exception as e: LOGGER.error(f"Error while processing tasks: {e}") finally: tasks = [] # Clear the list after processing - await asyncio.gather(*tasks) - tasks = [] # Clear the list after processing - # Process any remaining tasks when stopping + + # Process any remaining tasks when stopping if tasks: try: await asyncio.gather(*tasks) @@ -91,10 +100,6 @@ class DLTRecorder(threading.Thread): self.context_event_collector.stop() self.context_client.close() - #def create_topologies(self): - #topology_uuids = [DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME] - #create_missing_topologies(self.context_client, ADMIN_CONTEXT_ID, topology_uuids) - async def update_record(self, event: EventTypes) -> None: dlt_record_sender = DltRecordSender(self.context_client) await dlt_record_sender.initialize() # Ensure DltRecordSender is initialized asynchronously -- GitLab From ea35c20d9777a2673fe4bbb0307e4a2262c582b2 Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Wed, 28 Aug 2024 14:58:16 +0200 Subject: [PATCH 69/94] Async Implementation, Batches --- .../topology_abstractor/DltRecorder.py | 65 ++++++++++++------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 418a53612..97b48628a 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -65,40 +65,55 @@ class DLTRecorder(threading.Thread): create_context(self.context_client, DEFAULT_CONTEXT_NAME) #self.create_topologies() self.context_event_collector.start() - - tasks = [] + + batch_timeout = 1 # Time in seconds to wait before processing whatever tasks are available last_task_time = time.time() - + while not self.terminate.is_set(): event = self.context_event_collector.get_event(timeout=0.1) if event: - LOGGER.info('Processing Event({:s})...'.format(grpc_message_to_json_string(event))) - task = asyncio.create_task(self.update_record(event)) - tasks.append(task) - LOGGER.debug('Task for event scheduled.') - - # Update the last task time since we've added a new task - last_task_time = time.time() - - # Check if it's time to process the tasks or if we have enough tasks - if tasks and (len(tasks) >= 10 or (time.time() - last_task_time >= batch_timeout)): - try: - await asyncio.gather(*tasks) - except Exception as e: - LOGGER.error(f"Error while processing tasks: {e}") - finally: - tasks = [] # Clear the list after processing - - # Process any remaining tasks when stopping + LOGGER.info('Received Event({:s})...'.format(grpc_message_to_json_string(event))) + + # Prioritize the event based on its type + if event.event.event_type == 1: # CREATE + await self.create_event_queue.put(event) + elif event.event.event_type == 2: # UPDATE + await self.update_event_queue.put(event) + elif event.event.event_type == 3: # REMOVE + await self.remove_event_queue.put(event) + + # Check if it's time to process the tasks or if we have enough tasks + current_time = time.time() + if current_time - last_task_time >= batch_timeout: + await self.process_events() + last_task_time = current_time # Reset the timer after processing + + self.context_event_collector.stop() + self.context_client.close() + + async def process_events(self): + # Process CREATE events first + await self.process_queue(self.create_event_queue) + # Then process UPDATE events + await self.process_queue(self.update_event_queue) + # Finally, process REMOVE events + await self.process_queue(self.remove_event_queue) + + async def process_queue(self, queue: asyncio.Queue): + tasks = [] + while not queue.empty(): + event = await queue.get() + LOGGER.info('Processing Event({:s}) from queue...'.format(grpc_message_to_json_string(event))) + task = asyncio.create_task(self.update_record(event)) + tasks.append(task) + + # Execute tasks concurrently if tasks: try: await asyncio.gather(*tasks) except Exception as e: - LOGGER.error(f"Error while processing remaining tasks: {e}") - - self.context_event_collector.stop() - self.context_client.close() + LOGGER.error(f"Error while processing tasks: {e}") async def update_record(self, event: EventTypes) -> None: dlt_record_sender = DltRecordSender(self.context_client) -- GitLab From 522300c4c3b356a1bec869fb8b89b9a3312a84ff Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 4 Sep 2024 13:14:48 +0000 Subject: [PATCH 70/94] Pre-merge code cleanup --- deploy/all.sh | 2 +- deploy/tfs.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/all.sh b/deploy/all.sh index e77ff22ae..2b9e219ea 100755 --- a/deploy/all.sh +++ b/deploy/all.sh @@ -27,7 +27,7 @@ export TFS_REGISTRY_IMAGES=${TFS_REGISTRY_IMAGES:-"http://localhost:32000/tfs/"} # If not already set, set the list of components, separated by spaces, you want to build images for, and deploy. # By default, only basic components are deployed -export TFS_COMPONENTS=${TFS_COMPONENTS:-"context device ztp monitoring pathcomp service slice nbi webui load_generator dlt"} +export TFS_COMPONENTS=${TFS_COMPONENTS:-"context device pathcomp service slice nbi webui load_generator"} # If not already set, set the tag you want to use for your images. export TFS_IMAGE_TAG=${TFS_IMAGE_TAG:-"dev"} diff --git a/deploy/tfs.sh b/deploy/tfs.sh index 00bc2d48f..2c152fd60 100755 --- a/deploy/tfs.sh +++ b/deploy/tfs.sh @@ -27,7 +27,7 @@ export TFS_REGISTRY_IMAGES=${TFS_REGISTRY_IMAGES:-"http://localhost:32000/tfs/"} # If not already set, set the list of components, separated by spaces, you want to build images for, and deploy. # By default, only basic components are deployed -export TFS_COMPONENTS=${TFS_COMPONENTS:-"context device ztp monitoring pathcomp service slice nbi webui load_generator dlt"} +export TFS_COMPONENTS=${TFS_COMPONENTS:-"context device pathcomp service slice nbi webui load_generator"} # If not already set, set the tag you want to use for your images. export TFS_IMAGE_TAG=${TFS_IMAGE_TAG:-"dev"} -- GitLab From 01b8535d0320324fcb153357ca7a69d671ba732a Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 4 Sep 2024 13:19:17 +0000 Subject: [PATCH 71/94] Recovered legacy Gateway code --- src/dlt/gateway/legacy/Dockerfile | 41 ++++ src/dlt/gateway/legacy/README.md | 134 +++++++++++++ src/dlt/gateway/legacy/build.gradle.kts | 147 ++++++++++++++ .../config/ca.org1.example.com-cert.pem | 14 ++ .../legacy/config/connection-org1.json | 73 +++++++ src/dlt/gateway/legacy/gradle.properties | 1 + .../legacy/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + src/dlt/gateway/legacy/gradlew | 185 ++++++++++++++++++ src/dlt/gateway/legacy/gradlew.bat | 89 +++++++++ src/dlt/gateway/legacy/settings.gradle.kts | 19 ++ .../gateway/legacy/src/main/kotlin/Main.kt | 161 +++++++++++++++ .../src/main/kotlin/fabric/ConnectGateway.kt | 54 +++++ .../src/main/kotlin/fabric/EnrollAdmin.kt | 29 +++ .../src/main/kotlin/fabric/FabricConnector.kt | 178 +++++++++++++++++ .../src/main/kotlin/fabric/RegisterUser.kt | 65 ++++++ .../src/main/kotlin/grpc/FabricServer.kt | 94 +++++++++ .../src/main/kotlin/grpc/GrpcHandler.kt | 95 +++++++++ .../legacy/src/main/kotlin/proto/Config.proto | 54 +++++ 19 files changed, 1438 insertions(+) create mode 100644 src/dlt/gateway/legacy/Dockerfile create mode 100644 src/dlt/gateway/legacy/README.md create mode 100644 src/dlt/gateway/legacy/build.gradle.kts create mode 100644 src/dlt/gateway/legacy/config/ca.org1.example.com-cert.pem create mode 100644 src/dlt/gateway/legacy/config/connection-org1.json create mode 100644 src/dlt/gateway/legacy/gradle.properties create mode 100644 src/dlt/gateway/legacy/gradle/wrapper/gradle-wrapper.jar create mode 100644 src/dlt/gateway/legacy/gradle/wrapper/gradle-wrapper.properties create mode 100755 src/dlt/gateway/legacy/gradlew create mode 100644 src/dlt/gateway/legacy/gradlew.bat create mode 100644 src/dlt/gateway/legacy/settings.gradle.kts create mode 100644 src/dlt/gateway/legacy/src/main/kotlin/Main.kt create mode 100644 src/dlt/gateway/legacy/src/main/kotlin/fabric/ConnectGateway.kt create mode 100644 src/dlt/gateway/legacy/src/main/kotlin/fabric/EnrollAdmin.kt create mode 100644 src/dlt/gateway/legacy/src/main/kotlin/fabric/FabricConnector.kt create mode 100644 src/dlt/gateway/legacy/src/main/kotlin/fabric/RegisterUser.kt create mode 100644 src/dlt/gateway/legacy/src/main/kotlin/grpc/FabricServer.kt create mode 100644 src/dlt/gateway/legacy/src/main/kotlin/grpc/GrpcHandler.kt create mode 100644 src/dlt/gateway/legacy/src/main/kotlin/proto/Config.proto diff --git a/src/dlt/gateway/legacy/Dockerfile b/src/dlt/gateway/legacy/Dockerfile new file mode 100644 index 000000000..5b888b410 --- /dev/null +++ b/src/dlt/gateway/legacy/Dockerfile @@ -0,0 +1,41 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM zenika/kotlin:1.4-jdk12 + +# Make working directory move to it and copy DLT Gateway code +RUN mkdir -p /var/teraflow/dlt/gateway +WORKDIR /var/teraflow/dlt/gateway +COPY src/dlt/gateway/. ./ + +# Make directory for proto files and copy them +RUN mkdir proto +COPY proto/*.proto ./proto/ + +# Build DLT Gateway +RUN ./gradlew build + +EXPOSE 50051 + +# Create entrypoint.sh script +RUN echo "#!/bin/sh" > /entrypoint.sh +RUN echo "echo 195.37.154.24 peer0.org1.example.com >> /etc/hosts" >> /entrypoint.sh +RUN echo "echo 195.37.154.24 peer0.org2.example.com >> /etc/hosts" >> /entrypoint.sh +RUN echo "echo 195.37.154.24 orderer0.example.com >> /etc/hosts" >> /entrypoint.sh +RUN echo "cd /var/teraflow/dlt/gateway" >> /entrypoint.sh +RUN echo "./gradlew runServer" >> /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Gateway entry point +ENTRYPOINT ["sh", "/entrypoint.sh"] diff --git a/src/dlt/gateway/legacy/README.md b/src/dlt/gateway/legacy/README.md new file mode 100644 index 000000000..2cf6cfeb1 --- /dev/null +++ b/src/dlt/gateway/legacy/README.md @@ -0,0 +1,134 @@ +``` + NEC Laboratories Europe GmbH + + PROPRIETARY INFORMATION + + The software and its source code contain valuable trade secrets and + shall be maintained in confidence and treated as confidential + information. The software may only be used for evaluation and/or + testing purposes, unless otherwise explicitly stated in a written + agreement with NEC Laboratories Europe GmbH. + + Any unauthorized publication, transfer to third parties or + duplication of the object or source code - either totally or in + part - is strictly prohibited. + + Copyright (c) 2022 NEC Laboratories Europe GmbH + All Rights Reserved. + + Authors: Konstantin Munichev + + + NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE + WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND + THE ACCOMPANYING DOCUMENTATION. + + NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC + Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR + ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR + LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF + INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, + INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF + OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe + GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. + ``` + +# DLT module guide + +## General information +The DLT module is used to provide access to the underlying Fabric deployment. It allows clients +to add, retrieve, modify and delete blockchain-backed data, essentially working as a key-value +database. External clients should use gRPC API to communicate with this service, its detailed +description available below. + +## Code structure +The whole DLT module consists of several packages: +- fabric package +- http package +- proto package +- client example + +### Fabric package +The most important class in this package is `FabricConnector`. First, it establishes connection +with the underlying Fabric network using Java Gateway SDK. After that, it could be used as a +CRUD interface. +Other files contain auxiliary code for `FabricConnector` which allows it to register/enroll +users and to obtain smart contract instances. + +### Grpc package +Contains server side gRPC handler. It accepts requests from the outside and performs the +requested operation. For the more detailed description see Proto package description right below. + +### Proto package +The proto package contains `dlt.proto` file which defines gRPC service `DltService` API and messages +it uses. There are 3 main functions: `RecordToDlt` which allows to create/modify/delete data, +`GetFromDlt` which returns already written data and `SubscribeToDlt` which allows clients subscribe +for future create/modify/delete events with provided filters. +Other proto files don't play any significant role and could be safely ignored by end users. + +### Client example +This code is not necessary to the service, but it could be used to test the service. It contains +a sample gRPC client which connects the service and perform all the CRUD operations. + +# Fabric deployment notes + +## General notes +Current Fabric deployment uses Fabric test network with some additional helping scripts on top of it. +To start the network just run the `raft.sh` from `blockchain/scripts` directory. Use `stop.sh` +when you need to stop the network. + +## Server start preparations +To run the server it's necessary to copy certificate file +`fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem` +to the config folder (replacing the existing one). Also, it's necessary to copy `scripts/connection-org1.json` +file (again, replacing the old one). After copying, it must be edited. First, all `localhost` entrances +should be replaced with `teraflow.nlehd.de`. Second, `channel` section at the end of the file should be removed. +This should be done after every restart of the Fabric network. + +## Fabric configuration +Even though a test network is easy to deploy and use it's better to perform a custom configuration +for a production deployment. In practice every participating organization will likely prefer to have +its own Peer/Orderer/CA instances to prevent possible dependency on any other participants. This leads +not only to a better privacy/availability/security in general but also to the more complicated +deployment process as a side effect. Here we provide a very brief description of the most important points. + +### Organizations +Organization represents a network participant, which can be an individual, a large corporation or any other +entity. Each organization has its own CAs, orderers and peers. The recommendation here is to create an +organization entity for every independent participant and then decide how many CAs/peers/orderers does +every organization need and which channels should it has access to based on the exact project's goals. + +### Channels +Each channel represents an independent ledger with its own genesis block. Each transaction is executed +on a specific channel, and it's possible to define which organization has access to a given channel. +As a result channels are a pretty powerful privacy mechanism which allows to limit access to the private +data between organization. + +### Certificate authorities, peers and orderers +Certificate authorities (CA) are used to generate crypto materials for each organization. Two types of CA +exist: one is used to generate the certificates of the admin, the MSP and certificates of non-admin users. +Another type of CA is used to generate TLS certificates. As a result it's preferable to have at least two +CAs for every organization. + +Peers are entities which host ledgers and smart contracts. They communicate with applications and orderers, +receiving chaincode invocations (proposals), invoking chaincode, updating ledger when necessary and +returning result of execution. Peers can handle one or many ledgers, depending on the configuration. It's +very use case specific how many peers are necessary to the exact deployment. + +Orderers are used to execute a consensus in a distributing network making sure that every channel participant +has the same blocks with the same data. The default consensus algorithm is Raft which provides only a crash +fault tolerance. + +### Conclusion +As you can see, configuration procedure for Fabric is pretty tricky and includes quite a lot of entities. +In real world it will very likely involve participants from multiple organizations each of them performing +its own part of configuration. + +As a further reading it's recommended to start with the +[official deployment guide](https://hyperledger-fabric.readthedocs.io/en/release-2.2/deployment_guide_overview.html). +It contains a high level overview of a deployment process as well as links to the detailed descriptions to +CA/Peer/Orderer configuration descriptions. \ No newline at end of file diff --git a/src/dlt/gateway/legacy/build.gradle.kts b/src/dlt/gateway/legacy/build.gradle.kts new file mode 100644 index 000000000..b65aff89e --- /dev/null +++ b/src/dlt/gateway/legacy/build.gradle.kts @@ -0,0 +1,147 @@ +// NEC Laboratories Europe GmbH +// +// PROPRIETARY INFORMATION +// +// The software and its source code contain valuable trade secrets and +// shall be maintained in confidence and treated as confidential +// information. The software may only be used for evaluation and/or +// testing purposes, unless otherwise explicitly stated in a written +// agreement with NEC Laboratories Europe GmbH. +// +// Any unauthorized publication, transfer to third parties or +// duplication of the object or source code - either totally or in +// part - is strictly prohibited. +// +// Copyright (c) 2021 NEC Laboratories Europe GmbH +// All Rights Reserved. +// +// Authors: Konstantin Munichev +// +// +// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE +// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND +// THE ACCOMPANYING DOCUMENTATION. +// +// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC +// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR +// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR +// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF +// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, +// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF +// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe +// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. + +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +import com.google.protobuf.gradle.generateProtoTasks +import com.google.protobuf.gradle.id +import com.google.protobuf.gradle.plugins +import com.google.protobuf.gradle.protobuf +import com.google.protobuf.gradle.protoc + +ext["grpcVersion"] = "1.47.0" +ext["grpcKotlinVersion"] = "1.3.0" // CURRENT_GRPC_KOTLIN_VERSION +ext["protobufVersion"] = "3.20.1" +ext["ktorVersion"] = "1.6.5" + +plugins { + kotlin("jvm") version "1.6.21" + kotlin("plugin.serialization") version "1.4.21" + id("com.google.protobuf") version "0.8.18" + application +} + +group = "eu.neclab" +version = "1.0-SNAPSHOT" + +repositories { + mavenLocal() + google() + mavenCentral() +} + +dependencies { + implementation(kotlin("stdlib-jdk8")) + testImplementation("org.jetbrains.kotlin:kotlin-test:1.6.21") + implementation("javax.annotation:javax.annotation-api:1.3.2") + implementation("io.grpc:grpc-kotlin-stub:1.3.0") + implementation("io.grpc:grpc-protobuf:1.47.0") + implementation("com.google.protobuf:protobuf-kotlin:3.21.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3") + implementation("org.hyperledger.fabric:fabric-gateway-java:2.2.5") + implementation("ch.qos.logback:logback-classic:1.2.11") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.3.1") + runtimeOnly("io.grpc:grpc-netty:${rootProject.ext["grpcVersion"]}") +} + +tasks.test { + useJUnitPlatform() +} + +tasks.withType { + kotlinOptions.jvmTarget = "11" +} + +tasks.withType().all { + kotlinOptions { + freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn") + } +} + + +application { + mainClass.set("MainKt") +} + +task("runServer", JavaExec::class) { + main = "grpc.FabricServerKt" + classpath = sourceSets["main"].runtimeClasspath +} + + +sourceSets { + main { + proto { + srcDir("proto") + srcDir("src/main/kotlin/proto") + } + } +} + +sourceSets { + val main by getting { } + main.java.srcDirs("build/generated/source/proto/main/grpc") + main.java.srcDirs("build/generated/source/proto/main/grpckt") + main.java.srcDirs("build/generated/source/proto/main/java") + main.java.srcDirs("build/generated/source/proto/main/kotlin") +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${rootProject.ext["protobufVersion"]}" + } + plugins { + id("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:${rootProject.ext["grpcVersion"]}" + } + id("grpckt") { + artifact = "io.grpc:protoc-gen-grpc-kotlin:${rootProject.ext["grpcKotlinVersion"]}:jdk8@jar" + } + } + generateProtoTasks { + all().forEach { + it.plugins { + id("grpc") + id("grpckt") + } + it.builtins { + id("kotlin") + } + } + } +} diff --git a/src/dlt/gateway/legacy/config/ca.org1.example.com-cert.pem b/src/dlt/gateway/legacy/config/ca.org1.example.com-cert.pem new file mode 100644 index 000000000..d7fdf63cc --- /dev/null +++ b/src/dlt/gateway/legacy/config/ca.org1.example.com-cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICJzCCAc2gAwIBAgIUb5gDMfVeVdQjFkK3uC8LtlogN+gwCgYIKoZIzj0EAwIw +cDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQH +EwZEdXJoYW0xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh +Lm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwOTI3MDgzMDAwWhcNMzcwOTIzMDgzMDAw +WjBwMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExDzANBgNV +BAcTBkR1cmhhbTEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMT +Y2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDC3 +spCTT3pjfFXxkX/SFuBgWRiceR8rSoCNQOnIPeNGZK8xl2Zr7VuY06gqy9c+ecSU +PUWaXiCQxiLgZuS6TOWjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG +AQH/AgEBMB0GA1UdDgQWBBRFWSc7GZqcJJyJjXSEspzgAYInGzAKBggqhkjOPQQD +AgNIADBFAiEAodqc+adkiMuU6iv1IF8uJ/nMQbvMGoP3pb2827QzDosCICOw6W+y +uH03H3RO6KhOcS1ZzPjspyjrcC+dwzYX4DpW +-----END CERTIFICATE----- diff --git a/src/dlt/gateway/legacy/config/connection-org1.json b/src/dlt/gateway/legacy/config/connection-org1.json new file mode 100644 index 000000000..6f6f3f08d --- /dev/null +++ b/src/dlt/gateway/legacy/config/connection-org1.json @@ -0,0 +1,73 @@ +{ + "name": "test-network-org1", + "version": "1.0.0", + "client": { + "organization": "Org1", + "connection": { + "timeout": { + "peer": { + "endorser": "300" + } + } + } + }, + "organizations": { + "Org1": { + "mspid": "Org1MSP", + "peers": [ + "peer0.org1.example.com" + ], + "certificateAuthorities": [ + "ca.org1.example.com" + ] + } + }, + "peers": { + "peer0.org1.example.com": { + "url": "grpcs://teraflow.nlehd.de:7051", + "tlsCACerts": { + "pem": "-----BEGIN CERTIFICATE-----\nMIICJzCCAc2gAwIBAgIUb5gDMfVeVdQjFkK3uC8LtlogN+gwCgYIKoZIzj0EAwIw\ncDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQH\nEwZEdXJoYW0xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh\nLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwOTI3MDgzMDAwWhcNMzcwOTIzMDgzMDAw\nWjBwMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExDzANBgNV\nBAcTBkR1cmhhbTEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMT\nY2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDC3\nspCTT3pjfFXxkX/SFuBgWRiceR8rSoCNQOnIPeNGZK8xl2Zr7VuY06gqy9c+ecSU\nPUWaXiCQxiLgZuS6TOWjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\nAQH/AgEBMB0GA1UdDgQWBBRFWSc7GZqcJJyJjXSEspzgAYInGzAKBggqhkjOPQQD\nAgNIADBFAiEAodqc+adkiMuU6iv1IF8uJ/nMQbvMGoP3pb2827QzDosCICOw6W+y\nuH03H3RO6KhOcS1ZzPjspyjrcC+dwzYX4DpW\n-----END CERTIFICATE-----\n" + }, + "grpcOptions": { + "ssl-target-name-override": "peer0.org1.example.com", + "hostnameOverride": "peer0.org1.example.com" + } + }, + "peer0.org2.example.com": { + "url": "grpcs://teraflow.nlehd.de:9051", + "tlsCACerts": { + "pem": "-----BEGIN CERTIFICATE-----\nMIICHjCCAcWgAwIBAgIUL48scgv9ItATkBjSNhzYDjLUDsAwCgYIKoZIzj0EAwIw\nbDELMAkGA1UEBhMCVUsxEjAQBgNVBAgTCUhhbXBzaGlyZTEQMA4GA1UEBxMHSHVy\nc2xleTEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eub3Jn\nMi5leGFtcGxlLmNvbTAeFw0yMjA5MjcwODMwMDBaFw0zNzA5MjMwODMwMDBaMGwx\nCzAJBgNVBAYTAlVLMRIwEAYDVQQIEwlIYW1wc2hpcmUxEDAOBgNVBAcTB0h1cnNs\nZXkxGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2NhLm9yZzIu\nZXhhbXBsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ5qz8FfrEQ5S08\nr/avPyTrF2grXj5L4DnbvF4YEZ5Usnbm8Svovu7PO8uiVcwT5vrt6ssOdpBFZYu3\nNndpojnYo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAd\nBgNVHQ4EFgQUYcp7axYV9AaIptYQqhiCL0VDmXQwCgYIKoZIzj0EAwIDRwAwRAIg\nWT1V8/6flUPNcBkmbtEEKf83k7+6sR9k1a2wtVeJFnQCIE0ZSIL3k0dKQydQBpiz\nPcZZUULvQivcMlIsw5+mjIGc\n-----END CERTIFICATE-----\n" + }, + "grpcOptions": { + "ssl-target-name-override": "peer0.org2.example.com", + "hostnameOverride": "peer0.org2.example.com" + } + } + }, + "certificateAuthorities": { + "ca.org1.example.com": { + "url": "https://teraflow.nlehd.de:7054", + "caName": "ca-org1", + "tlsCACerts": { + "pem": [ + "-----BEGIN CERTIFICATE-----\nMIICJzCCAc2gAwIBAgIUb5gDMfVeVdQjFkK3uC8LtlogN+gwCgYIKoZIzj0EAwIw\ncDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQH\nEwZEdXJoYW0xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh\nLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwOTI3MDgzMDAwWhcNMzcwOTIzMDgzMDAw\nWjBwMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExDzANBgNV\nBAcTBkR1cmhhbTEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMT\nY2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDC3\nspCTT3pjfFXxkX/SFuBgWRiceR8rSoCNQOnIPeNGZK8xl2Zr7VuY06gqy9c+ecSU\nPUWaXiCQxiLgZuS6TOWjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\nAQH/AgEBMB0GA1UdDgQWBBRFWSc7GZqcJJyJjXSEspzgAYInGzAKBggqhkjOPQQD\nAgNIADBFAiEAodqc+adkiMuU6iv1IF8uJ/nMQbvMGoP3pb2827QzDosCICOw6W+y\nuH03H3RO6KhOcS1ZzPjspyjrcC+dwzYX4DpW\n-----END CERTIFICATE-----\n" + ] + }, + "httpOptions": { + "verify": false + } + } + }, + "orderers": { + "orderer0.example.com": { + "url": "grpcs://teraflow.nlehd.de:7050", + "tlsCACerts": { + "pem": "-----BEGIN CERTIFICATE-----\nMIICCzCCAbGgAwIBAgIUdZQo3q4OqyxIkidmAV4QkewCylIwCgYIKoZIzj0EAwIw\nYjELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQHEwhOZXcg\nWW9yazEUMBIGA1UEChMLZXhhbXBsZS5jb20xFzAVBgNVBAMTDmNhLmV4YW1wbGUu\nY29tMB4XDTIyMDkyNzA4MzAwMFoXDTM3MDkyMzA4MzAwMFowYjELMAkGA1UEBhMC\nVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQHEwhOZXcgWW9yazEUMBIGA1UE\nChMLZXhhbXBsZS5jb20xFzAVBgNVBAMTDmNhLmV4YW1wbGUuY29tMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAERR0UzsHSFoyON+9Noxmk1IhnTvSdLWGgEpEwrqVr\n5DwitkeJwRWq134JBTmXuZzsUG87oN6Hr94XAEe4j9Zq8qNFMEMwDgYDVR0PAQH/\nBAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFN8XsELp/X0akrlJ\nY3/BWo2jZS3cMAoGCCqGSM49BAMCA0gAMEUCIQCZYYXW/0h3Kq4BmROpOHfrondg\nopf5LndeujYlH3i8tQIgCtpTQiDXZd+IAUduRmn7a46CwJSbjYbXFVX5vumIbE4=\n-----END CERTIFICATE-----\n" + }, + "grpcOptions": { + "ssl-target-name-override": "orderer0.example.com", + "hostnameOverride": "orderer0.example.com" + } + } + } +} diff --git a/src/dlt/gateway/legacy/gradle.properties b/src/dlt/gateway/legacy/gradle.properties new file mode 100644 index 000000000..7fc6f1ff2 --- /dev/null +++ b/src/dlt/gateway/legacy/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/src/dlt/gateway/legacy/gradle/wrapper/gradle-wrapper.jar b/src/dlt/gateway/legacy/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/src/dlt/gateway/legacy/gradle/wrapper/gradle-wrapper.properties b/src/dlt/gateway/legacy/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..69a971507 --- /dev/null +++ b/src/dlt/gateway/legacy/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/src/dlt/gateway/legacy/gradlew b/src/dlt/gateway/legacy/gradlew new file mode 100755 index 000000000..744e882ed --- /dev/null +++ b/src/dlt/gateway/legacy/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/src/dlt/gateway/legacy/gradlew.bat b/src/dlt/gateway/legacy/gradlew.bat new file mode 100644 index 000000000..ac1b06f93 --- /dev/null +++ b/src/dlt/gateway/legacy/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/dlt/gateway/legacy/settings.gradle.kts b/src/dlt/gateway/legacy/settings.gradle.kts new file mode 100644 index 000000000..77fa0f0b2 --- /dev/null +++ b/src/dlt/gateway/legacy/settings.gradle.kts @@ -0,0 +1,19 @@ +/* + * Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +rootProject.name = "gateway" + diff --git a/src/dlt/gateway/legacy/src/main/kotlin/Main.kt b/src/dlt/gateway/legacy/src/main/kotlin/Main.kt new file mode 100644 index 000000000..c57c9e980 --- /dev/null +++ b/src/dlt/gateway/legacy/src/main/kotlin/Main.kt @@ -0,0 +1,161 @@ +// NEC Laboratories Europe GmbH +// +// PROPRIETARY INFORMATION +// +// The software and its source code contain valuable trade secrets and +// shall be maintained in confidence and treated as confidential +// information. The software may only be used for evaluation and/or +// testing purposes, unless otherwise explicitly stated in a written +// agreement with NEC Laboratories Europe GmbH. +// +// Any unauthorized publication, transfer to third parties or +// duplication of the object or source code - either totally or in +// part - is strictly prohibited. +// +// Copyright (c) 2022 NEC Laboratories Europe GmbH +// All Rights Reserved. +// +// Authors: Konstantin Munichev +// +// +// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE +// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND +// THE ACCOMPANYING DOCUMENTATION. +// +// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC +// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR +// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR +// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF +// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, +// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF +// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe +// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. + +import context.ContextOuterClass +import io.grpc.ManagedChannel +import io.grpc.ManagedChannelBuilder +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import dlt.DltGateway +import dlt.DltGatewayServiceGrpcKt +import java.io.Closeable +import java.util.* +import java.util.concurrent.TimeUnit + +class DltServiceClient(private val channel: ManagedChannel) : Closeable { + private val stub: DltGatewayServiceGrpcKt.DltGatewayServiceCoroutineStub = + DltGatewayServiceGrpcKt.DltGatewayServiceCoroutineStub(channel) + + suspend fun putData(data: DltGateway.DltRecord) { + println("Sending record ${data.recordId}...") + val response = stub.recordToDlt(data) + println("Response: ${response.recordId}") + } + + suspend fun getData(id: DltGateway.DltRecordId) { + println("Requesting record $id...") + val response = stub.getFromDlt(id) + println("Got data: $response") + } + + fun subscribe(filter: DltGateway.DltRecordSubscription) { + val subscription = stub.subscribeToDlt(filter) + GlobalScope.launch { + subscription.collect { + println("Got subscription event") + println(it) + } + } + } + + override fun close() { + channel.shutdown().awaitTermination(5, TimeUnit.SECONDS) + } +} + + +fun main() = runBlocking { + val port = 50051 + val channel = ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build() + + val client = DltServiceClient(channel) + + val domainUuid = UUID.randomUUID().toString() + val recordUuid = UUID.randomUUID().toString() + println("New domain uuid $domainUuid") + println("New record uuid $recordUuid") + + val id = DltGateway.DltRecordId.newBuilder() + .setDomainUuid( + ContextOuterClass.Uuid.newBuilder() + .setUuid(domainUuid) + ) + .setRecordUuid( + ContextOuterClass.Uuid.newBuilder() + .setUuid(recordUuid) + ) + .setType(DltGateway.DltRecordTypeEnum.DLTRECORDTYPE_SERVICE) + .build() + + val subscription = DltGateway.DltRecordSubscription.newBuilder() + .addType(DltGateway.DltRecordTypeEnum.DLTRECORDTYPE_CONTEXT) + .addType(DltGateway.DltRecordTypeEnum.DLTRECORDTYPE_LINK) + .addType(DltGateway.DltRecordTypeEnum.DLTRECORDTYPE_SERVICE) + .addOperation(DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_ADD) + .addOperation(DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_UPDATE) + .addOperation(DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_DELETE) + .build() + + client.subscribe(subscription) + + Thread.sleep(5000) + + val data = DltGateway.DltRecord.newBuilder() + .setRecordId(id) + .setOperation(DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_ADD) + .setDataJson("\"{\"device_config\": {\"config_rules\": []}, \"device_drivers\": []," + + "\"device_endpoints\": [], \"device_id\": {\"device_uuid\": {\"uuid\": \"dev-12345\"}}," + + "\"device_operational_status\": \"DEVICEOPERATIONALSTATUS_ENABLED\"," + + "\"device_type\": \"packet-router\"}\", \"operation\": \"DLTRECORDOPERATION_ADD\"," + + "\"record_id\": {\"domain_uuid\": {\"uuid\": \"tfs-a\"}, \"record_uuid\": {\"uuid\": \"dev-12345\"}," + + "\"type\": \"DLTRECORDTYPE_DEVICE\"}") + .build() + + println("sending new record") + client.putData(data) + client.getData(id) + + Thread.sleep(5000) + + val updateData = DltGateway.DltRecord.newBuilder() + .setRecordId(id) + .setOperation(DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_UPDATE) + .setDataJson("{\"name\": \"test\"}") + .build() + + println("updating record") + client.putData(updateData) + client.getData(id) + + Thread.sleep(5000) + + val removeData = DltGateway.DltRecord.newBuilder() + .setRecordId(id) + .setOperation(DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_DELETE) + .setDataJson("{\"name\": \"test\"}") + .build() + + println("removing record") + client.putData(removeData) + try { + client.getData(id) + } catch (e: Exception) { + println(e.toString()) + } + Thread.sleep(5000) +} diff --git a/src/dlt/gateway/legacy/src/main/kotlin/fabric/ConnectGateway.kt b/src/dlt/gateway/legacy/src/main/kotlin/fabric/ConnectGateway.kt new file mode 100644 index 000000000..00ec40d57 --- /dev/null +++ b/src/dlt/gateway/legacy/src/main/kotlin/fabric/ConnectGateway.kt @@ -0,0 +1,54 @@ +// NEC Laboratories Europe GmbH +// +// PROPRIETARY INFORMATION +// +// The software and its source code contain valuable trade secrets and +// shall be maintained in confidence and treated as confidential +// information. The software may only be used for evaluation and/or +// testing purposes, unless otherwise explicitly stated in a written +// agreement with NEC Laboratories Europe GmbH. +// +// Any unauthorized publication, transfer to third parties or +// duplication of the object or source code - either totally or in +// part - is strictly prohibited. +// +// Copyright (c) 2022 NEC Laboratories Europe GmbH +// All Rights Reserved. +// +// Authors: Konstantin Munichev +// +// +// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE +// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND +// THE ACCOMPANYING DOCUMENTATION. +// +// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC +// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR +// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR +// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF +// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, +// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF +// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe +// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. + +package fabric + +import org.hyperledger.fabric.gateway.Contract +import org.hyperledger.fabric.gateway.Gateway +import org.hyperledger.fabric.gateway.Wallet +import java.nio.file.Paths + +// helper function for getting connected to the gateway +fun getContract(config: proto.Config.DltConfig, wallet: Wallet): Contract { + // load a CCP + val networkConfigPath = Paths.get(config.connectionFile) + val builder = Gateway.createBuilder() + builder.identity(wallet, config.user).networkConfig(networkConfigPath).discovery(true) + val gateway = builder.connect() + val network = gateway.getNetwork(config.channel) + return network.getContract(config.contract) +} \ No newline at end of file diff --git a/src/dlt/gateway/legacy/src/main/kotlin/fabric/EnrollAdmin.kt b/src/dlt/gateway/legacy/src/main/kotlin/fabric/EnrollAdmin.kt new file mode 100644 index 000000000..b44202719 --- /dev/null +++ b/src/dlt/gateway/legacy/src/main/kotlin/fabric/EnrollAdmin.kt @@ -0,0 +1,29 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package fabric + +import org.hyperledger.fabric.gateway.Identities +import org.hyperledger.fabric.gateway.Wallet +import org.hyperledger.fabric_ca.sdk.EnrollmentRequest +import org.hyperledger.fabric_ca.sdk.HFCAClient + +fun enrollAdmin(config: proto.Config.DltConfig, caClient: HFCAClient, wallet: Wallet) { + // Check to see if we've already enrolled the admin user. + if (wallet.get(config.caAdmin) != null) { + println("An identity for the admin user ${config.caAdmin} already exists in the wallet") + return + } + + // Enroll the admin user, and import the new identity into the wallet. + val enrollmentRequestTLS = EnrollmentRequest() + enrollmentRequestTLS.addHost(config.caUrl) + enrollmentRequestTLS.profile = "tls" + val enrollment = caClient.enroll(config.caAdmin, config.caAdminSecret, enrollmentRequestTLS) + val user = Identities.newX509Identity(config.msp, enrollment) + wallet.put(config.caAdmin, user) + println("Successfully enrolled user ${config.caAdmin} and imported it into the wallet") +} diff --git a/src/dlt/gateway/legacy/src/main/kotlin/fabric/FabricConnector.kt b/src/dlt/gateway/legacy/src/main/kotlin/fabric/FabricConnector.kt new file mode 100644 index 000000000..af6592be9 --- /dev/null +++ b/src/dlt/gateway/legacy/src/main/kotlin/fabric/FabricConnector.kt @@ -0,0 +1,178 @@ +// NEC Laboratories Europe GmbH +// +// PROPRIETARY INFORMATION +// +// The software and its source code contain valuable trade secrets and +// shall be maintained in confidence and treated as confidential +// information. The software may only be used for evaluation and/or +// testing purposes, unless otherwise explicitly stated in a written +// agreement with NEC Laboratories Europe GmbH. +// +// Any unauthorized publication, transfer to third parties or +// duplication of the object or source code - either totally or in +// part - is strictly prohibited. +// +// Copyright (c) 2022 NEC Laboratories Europe GmbH +// All Rights Reserved. +// +// Authors: Konstantin Munichev +// +// +// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE +// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND +// THE ACCOMPANYING DOCUMENTATION. +// +// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC +// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR +// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR +// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF +// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, +// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF +// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe +// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. + +package fabric + +import context.ContextOuterClass +import dlt.DltGateway.DltRecord +import dlt.DltGateway.DltRecordEvent +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.runBlocking +import org.hyperledger.fabric.gateway.Contract +import org.hyperledger.fabric.gateway.ContractEvent +import org.hyperledger.fabric.gateway.Wallet +import org.hyperledger.fabric.gateway.Wallets +import org.hyperledger.fabric.sdk.security.CryptoSuiteFactory +import org.hyperledger.fabric_ca.sdk.HFCAClient +import proto.Config +import java.nio.file.Paths +import java.util.* +import java.util.function.Consumer + +class FabricConnector(val config: Config.DltConfig) { + private val caClient: HFCAClient + private val wallet: Wallet + private val contract: Contract + + private val channels: MutableList> = mutableListOf() + + private val encoder: Base64.Encoder = Base64.getEncoder() + private val decoder: Base64.Decoder = Base64.getDecoder() + + init { + // Create a CA client for interacting with the CA. + val props = Properties() + props["pemFile"] = config.caCertFile + props["allowAllHostNames"] = "true" + caClient = HFCAClient.createNewInstance(config.caUrl, props) + val cryptoSuite = CryptoSuiteFactory.getDefault().cryptoSuite + caClient.cryptoSuite = cryptoSuite + + // Create a wallet for managing identities + wallet = Wallets.newFileSystemWallet(Paths.get(config.wallet)) + contract = connect() + + fabricSubscribe() + } + + private fun fabricSubscribe() { + val consumer = Consumer { event: ContractEvent? -> + run { + println("new event detected") + val record = DltRecord.parseFrom(decoder.decode(event?.payload?.get())) + println(record.recordId.recordUuid) + val eventType: ContextOuterClass.EventTypeEnum = when (event?.name) { + "Add" -> ContextOuterClass.EventTypeEnum.EVENTTYPE_CREATE + "Update" -> ContextOuterClass.EventTypeEnum.EVENTTYPE_UPDATE + "Remove" -> ContextOuterClass.EventTypeEnum.EVENTTYPE_REMOVE + else -> ContextOuterClass.EventTypeEnum.EVENTTYPE_UNDEFINED + } + val pbEvent = DltRecordEvent.newBuilder() + .setEvent( + ContextOuterClass.Event.newBuilder() + .setTimestamp( + ContextOuterClass.Timestamp.newBuilder() + .setTimestamp(System.currentTimeMillis().toDouble()) + ) + .setEventType(eventType) + ) + .setRecordId(record.recordId) + .build() + + runBlocking { + channels.forEach { + it.trySend(pbEvent) + } + } + } + } + contract.addContractListener(consumer) + } + + fun connect(): Contract { + enrollAdmin(config, caClient, wallet) + registerUser(config, caClient, wallet) + return getContract(config, wallet) + } + + fun putData(record: DltRecord): String { + println(record.toString()) + + try { + contract.submitTransaction( + "AddRecord", + record.recordId.recordUuid.uuid, + encoder.encodeToString(record.toByteArray()) + ) + } catch (e: Exception) { + println(e.toString()) + return e.toString() + } + return "" + } + + fun getData(uuid: String): DltRecord { + return try { + val result = contract.evaluateTransaction("GetRecord", uuid) + DltRecord.parseFrom(decoder.decode(result)) + } catch (e: Exception) { + println(e.toString()) + DltRecord.getDefaultInstance() + } + } + + fun updateData(record: DltRecord): String { + try { + contract.submitTransaction( + "UpdateRecord", + record.recordId.recordUuid.uuid, + encoder.encodeToString(record.toByteArray()) + ) + } catch (e: Exception) { + return e.toString() + } + return "" + } + + fun deleteData(record: DltRecord): String { + try { + contract.submitTransaction( + "DeleteRecord", + record.recordId.recordUuid.uuid, + ) + } catch (e: Exception) { + return e.toString() + } + return "" + } + + fun subscribeForEvents(): Channel { + val produceCh = Channel() + channels.add(produceCh) + return produceCh + } +} \ No newline at end of file diff --git a/src/dlt/gateway/legacy/src/main/kotlin/fabric/RegisterUser.kt b/src/dlt/gateway/legacy/src/main/kotlin/fabric/RegisterUser.kt new file mode 100644 index 000000000..fb5cc2969 --- /dev/null +++ b/src/dlt/gateway/legacy/src/main/kotlin/fabric/RegisterUser.kt @@ -0,0 +1,65 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ +package fabric + +import org.hyperledger.fabric.gateway.Identities +import org.hyperledger.fabric.gateway.Wallet +import org.hyperledger.fabric.gateway.X509Identity +import org.hyperledger.fabric.sdk.Enrollment +import org.hyperledger.fabric.sdk.User +import org.hyperledger.fabric_ca.sdk.HFCAClient +import org.hyperledger.fabric_ca.sdk.RegistrationRequest +import java.security.PrivateKey + +fun registerUser(config: proto.Config.DltConfig, caClient: HFCAClient, wallet: Wallet) { + // Check to see if we've already enrolled the user. + if (wallet[config.user] != null) { + println("An identity for the user ${config.user} already exists in the wallet") + return + } + val adminIdentity = wallet[config.caAdmin] as X509Identity + val admin = object : User { + override fun getName(): String { + return config.caAdmin + } + + override fun getRoles(): Set? { + return null + } + + override fun getAccount(): String? { + return null + } + + override fun getAffiliation(): String { + return config.affiliation + } + + override fun getEnrollment(): Enrollment { + return object : Enrollment { + override fun getKey(): PrivateKey { + return adminIdentity.privateKey + } + + override fun getCert(): String { + return Identities.toPemString(adminIdentity.certificate) + } + } + } + + override fun getMspId(): String { + return config.msp + } + } + + // Register the user, enroll the user, and import the new identity into the wallet. + val registrationRequest = RegistrationRequest(config.user) + registrationRequest.affiliation = config.affiliation + registrationRequest.enrollmentID = config.user + val enrollmentSecret = caClient.register(registrationRequest, admin) + val enrollment = caClient.enroll(config.user, enrollmentSecret) + val user = Identities.newX509Identity(config.msp, enrollment) + wallet.put(config.user, user) + println("Successfully enrolled user ${config.user} and imported it into the wallet") +} diff --git a/src/dlt/gateway/legacy/src/main/kotlin/grpc/FabricServer.kt b/src/dlt/gateway/legacy/src/main/kotlin/grpc/FabricServer.kt new file mode 100644 index 000000000..9b4e1f4dc --- /dev/null +++ b/src/dlt/gateway/legacy/src/main/kotlin/grpc/FabricServer.kt @@ -0,0 +1,94 @@ +// NEC Laboratories Europe GmbH +// +// PROPRIETARY INFORMATION +// +// The software and its source code contain valuable trade secrets and +// shall be maintained in confidence and treated as confidential +// information. The software may only be used for evaluation and/or +// testing purposes, unless otherwise explicitly stated in a written +// agreement with NEC Laboratories Europe GmbH. +// +// Any unauthorized publication, transfer to third parties or +// duplication of the object or source code - either totally or in +// part - is strictly prohibited. +// +// Copyright (c) 2022 NEC Laboratories Europe GmbH +// All Rights Reserved. +// +// Authors: Konstantin Munichev +// +// +// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE +// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND +// THE ACCOMPANYING DOCUMENTATION. +// +// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC +// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR +// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR +// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF +// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, +// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF +// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe +// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. + +package grpc + +import fabric.FabricConnector +import io.grpc.Server +import io.grpc.ServerBuilder +import proto.Config +import kotlin.random.Random +import kotlin.random.nextUInt + +class FabricServer(val port: Int) { + private val server: Server + + init { + val id = Random.nextUInt() + val cfg = Config.DltConfig.newBuilder().setWallet("wallet$id").setConnectionFile("config/connection-org1.json") + .setUser("appUser$id") + .setChannel("dlt") + .setContract("basic").setCaCertFile("config/ca.org1.example.com-cert.pem").setCaUrl("https://teraflow.nlehd.de:7054") + .setCaAdmin("admin").setCaAdminSecret("adminpw").setMsp("Org1MSP").setAffiliation("org1.department1") + .build() + val connector = FabricConnector(cfg) + + val dltService = DLTService(connector) + server = ServerBuilder + .forPort(port) + .addService(dltService) + .build() + + } + + fun start() { + server.start() + println("Server started, listening on $port") + Runtime.getRuntime().addShutdownHook( + Thread { + println("Shutting down...") + this@FabricServer.stop() + println("Server shut down") + } + ) + } + + private fun stop() { + server.shutdown() + } + + fun blockUntilShutdown() { + server.awaitTermination() + } +} + +fun main() { + val port = 50051 + val server = FabricServer(port) + server.start() + server.blockUntilShutdown() +} diff --git a/src/dlt/gateway/legacy/src/main/kotlin/grpc/GrpcHandler.kt b/src/dlt/gateway/legacy/src/main/kotlin/grpc/GrpcHandler.kt new file mode 100644 index 000000000..d39c24a1a --- /dev/null +++ b/src/dlt/gateway/legacy/src/main/kotlin/grpc/GrpcHandler.kt @@ -0,0 +1,95 @@ +// NEC Laboratories Europe GmbH +// +// PROPRIETARY INFORMATION +// +// The software and its source code contain valuable trade secrets and +// shall be maintained in confidence and treated as confidential +// information. The software may only be used for evaluation and/or +// testing purposes, unless otherwise explicitly stated in a written +// agreement with NEC Laboratories Europe GmbH. +// +// Any unauthorized publication, transfer to third parties or +// duplication of the object or source code - either totally or in +// part - is strictly prohibited. +// +// Copyright (c) 2022 NEC Laboratories Europe GmbH +// All Rights Reserved. +// +// Authors: Konstantin Munichev +// +// +// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE +// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND +// THE ACCOMPANYING DOCUMENTATION. +// +// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC +// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR +// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR +// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF +// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, +// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF +// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe +// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. + +package grpc + +import fabric.FabricConnector +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.consumeAsFlow +import context.ContextOuterClass +import dlt.DltGateway +import dlt.DltGatewayServiceGrpcKt + +class DLTService(private val connector: FabricConnector) : + DltGatewayServiceGrpcKt.DltGatewayServiceCoroutineImplBase() { + override suspend fun recordToDlt(request: DltGateway.DltRecord): DltGateway.DltRecordStatus { + println("Incoming request ${request.recordId.recordUuid}") + val error = when (request.operation) { + DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_ADD -> { + println("Adding new record") + connector.putData(request) + } + DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_UPDATE -> { + println("Updating record") + connector.updateData(request) + } + DltGateway.DltRecordOperationEnum.DLTRECORDOPERATION_DELETE -> { + println("Deleting record") + connector.deleteData(request) + } + else -> "Undefined or unknown operation" + } + + val dltStatusEnum: DltGateway.DltRecordStatusEnum = if (error == "") { + DltGateway.DltRecordStatusEnum.DLTRECORDSTATUS_SUCCEEDED + } else { + DltGateway.DltRecordStatusEnum.DLTRECORDSTATUS_FAILED + } + return DltGateway.DltRecordStatus.newBuilder() + .setRecordId(request.recordId) + .setStatus(dltStatusEnum) + .setErrorMessage(error) + .build() + } + + override suspend fun getFromDlt(request: DltGateway.DltRecordId): DltGateway.DltRecord { + return connector.getData(request.recordUuid.uuid) + } + + override fun subscribeToDlt(request: DltGateway.DltRecordSubscription): Flow { + println("Subscription request: $request") + return connector.subscribeForEvents().consumeAsFlow() + } + + override suspend fun getDltStatus(request: ContextOuterClass.TeraFlowController): DltGateway.DltPeerStatus { + return super.getDltStatus(request) + } + + override suspend fun getDltPeers(request: ContextOuterClass.Empty): DltGateway.DltPeerStatusList { + return super.getDltPeers(request) + } +} \ No newline at end of file diff --git a/src/dlt/gateway/legacy/src/main/kotlin/proto/Config.proto b/src/dlt/gateway/legacy/src/main/kotlin/proto/Config.proto new file mode 100644 index 000000000..b6d4c5614 --- /dev/null +++ b/src/dlt/gateway/legacy/src/main/kotlin/proto/Config.proto @@ -0,0 +1,54 @@ +// NEC Laboratories Europe GmbH +// +// PROPRIETARY INFORMATION +// +// The software and its source code contain valuable trade secrets and +// shall be maintained in confidence and treated as confidential +// information. The software may only be used for evaluation and/or +// testing purposes, unless otherwise explicitly stated in a written +// agreement with NEC Laboratories Europe GmbH. +// +// Any unauthorized publication, transfer to third parties or +// duplication of the object or source code - either totally or in +// part - is strictly prohibited. +// +// Copyright (c) 2022 NEC Laboratories Europe GmbH +// All Rights Reserved. +// +// Authors: Konstantin Munichev +// +// +// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE +// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND +// THE ACCOMPANYING DOCUMENTATION. +// +// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC +// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR +// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR +// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF +// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, +// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF +// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe +// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. + +syntax = "proto3"; + +package proto; + +message DltConfig { + string wallet = 1; + string connectionFile = 2; + string user = 3; + string channel = 4; + string contract = 5; + string caCertFile = 6; + string caUrl = 7; + string caAdmin = 8; + string caAdminSecret = 9; + string msp = 10; + string affiliation = 11; +} -- GitLab From d0d04e247d3aa906e89d45b68c610ba6b854a9d5 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 4 Sep 2024 13:34:38 +0000 Subject: [PATCH 72/94] Pre-merge code cleanup --- manifests/dltservice.yaml | 207 +++++++++++++++++++------------------- 1 file changed, 101 insertions(+), 106 deletions(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index 6602a45f5..04f0921ca 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -20,7 +20,7 @@ data: CHANNEL_NAME: "channel1" CHAINCODE_NAME: "adrenalineDLT" MSP_ID: "Org1MSP" - PEER_ENDPOINT: "10.1.1.96:7051" #Change to required peer# + PEER_ENDPOINT: "10.1.1.96:7051" #Change to required peer# PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" CRYPTO_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com" KEY_DIRECTORY_PATH: "/etc/hyperledger/fabric-keystore/keystore" @@ -28,7 +28,6 @@ data: TLS_CERT_PATH: "/etc/hyperledger/fabric-ca-crt/ca.crt" --- - apiVersion: apps/v1 kind: Deployment metadata: @@ -44,111 +43,110 @@ spec: spec: terminationGracePeriodSeconds: 5 containers: - - name: connector - image: labs.etsi.org:5050/tfs/controller/dlt-connector:latest - imagePullPolicy: Always - ports: - - containerPort: 8080 - - containerPort: 9192 - env: - - name: LOG_LEVEL - value: "INFO" - ## for debug purposes - #- name: DLT_GATEWAY_HOST - # value: "mock-blockchain.tfs-bchain.svc.cluster.local" - #- name: DLT_GATEWAY_PORT - # value: "50051" - readinessProbe: - exec: - command: ["/bin/grpc_health_probe", "-addr=:8080"] - livenessProbe: - exec: - command: ["/bin/grpc_health_probe", "-addr=:8080"] - resources: - requests: - cpu: 50m - memory: 64Mi - limits: - cpu: 500m - memory: 512Mi - - name: gateway - image: labs.etsi.org:5050/tfs/controller/dlt-gateway:latest - imagePullPolicy: Always - ports: - - containerPort: 50051 - resources: - requests: - cpu: 200m - memory: 512Mi - limits: - cpu: 700m - memory: 1024Mi - volumeMounts: - - mountPath: /test-network - name: dlt-volume - readOnly: true + - name: connector + image: labs.etsi.org:5050/tfs/controller/dlt-connector:latest + imagePullPolicy: Always + ports: + - containerPort: 8080 + - containerPort: 9192 + env: + - name: LOG_LEVEL + value: "INFO" + ## for debug purposes + #- name: DLT_GATEWAY_HOST + # value: "mock-blockchain.tfs-bchain.svc.cluster.local" + #- name: DLT_GATEWAY_PORT + # value: "50051" + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:8080"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:8080"] + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 500m + memory: 512Mi + - name: gateway + image: labs.etsi.org:5050/tfs/controller/dlt-gateway:latest + imagePullPolicy: Always + ports: + - containerPort: 50051 + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 700m + memory: 1024Mi + volumeMounts: + - mountPath: /test-network + name: dlt-volume + readOnly: true + - name: keystore + mountPath: /etc/hyperledger/fabric-keystore + readOnly: true + - name: signcerts + mountPath: /etc/hyperledger/fabric-signcerts + readOnly: true + - name: ca-crt + mountPath: /etc/hyperledger/fabric-ca-crt + readOnly: true + env: + - name: CHANNEL_NAME + valueFrom: + configMapKeyRef: + name: dlt-config + key: CHANNEL_NAME + - name: CHAINCODE_NAME + valueFrom: + configMapKeyRef: + name: dlt-config + key: CHAINCODE_NAME + - name: MSP_ID + valueFrom: + configMapKeyRef: + name: dlt-config + key: MSP_ID + - name: PEER_ENDPOINT + valueFrom: + configMapKeyRef: + name: dlt-config + key: PEER_ENDPOINT + - name: PEER_HOST_ALIAS + valueFrom: + configMapKeyRef: + name: dlt-config + key: PEER_HOST_ALIAS + - name: CRYPTO_PATH + valueFrom: + configMapKeyRef: + name: dlt-config + key: CRYPTO_PATH + - name: KEY_DIRECTORY_PATH + value: "/etc/hyperledger/fabric-keystore/keystore" + - name: CERT_DIRECTORY_PATH + value: "/etc/hyperledger/fabric-signcerts/signcerts.pem" + - name: TLS_CERT_PATH + value: "/etc/hyperledger/fabric-ca-crt/ca.crt" + volumes: + - name: dlt-volume + persistentVolumeClaim: + claimName: dlt-pvc - name: keystore - mountPath: /etc/hyperledger/fabric-keystore - readOnly: true + secret: + secretName: dlt-keystone - name: signcerts - mountPath: /etc/hyperledger/fabric-signcerts - readOnly: true + secret: + secretName: dlt-signcerts - name: ca-crt - mountPath: /etc/hyperledger/fabric-ca-crt - readOnly: true - env: - - name: CHANNEL_NAME - valueFrom: - configMapKeyRef: - name: dlt-config - key: CHANNEL_NAME - - name: CHAINCODE_NAME - valueFrom: - configMapKeyRef: - name: dlt-config - key: CHAINCODE_NAME - - name: MSP_ID - valueFrom: - configMapKeyRef: - name: dlt-config - key: MSP_ID - - name: PEER_ENDPOINT - valueFrom: - configMapKeyRef: - name: dlt-config - key: PEER_ENDPOINT - - name: PEER_HOST_ALIAS - valueFrom: - configMapKeyRef: - name: dlt-config - key: PEER_HOST_ALIAS - - name: CRYPTO_PATH - valueFrom: - configMapKeyRef: - name: dlt-config - key: CRYPTO_PATH - - name: KEY_DIRECTORY_PATH - value: "/etc/hyperledger/fabric-keystore/keystore" - - name: CERT_DIRECTORY_PATH - value: "/etc/hyperledger/fabric-signcerts/signcerts.pem" - - name: TLS_CERT_PATH - value: "/etc/hyperledger/fabric-ca-crt/ca.crt" - volumes: - - name: dlt-volume - persistentVolumeClaim: - claimName: dlt-pvc - - name: keystore - secret: - secretName: dlt-keystone - - name: signcerts - secret: - secretName: dlt-signcerts - - name: ca-crt - secret: - secretName: dlt-ca-crt + secret: + secretName: dlt-ca-crt --- - apiVersion: v1 kind: PersistentVolumeClaim metadata: @@ -161,7 +159,6 @@ spec: storage: 1Gi --- - apiVersion: v1 kind: PersistentVolume metadata: @@ -173,12 +170,11 @@ spec: - ReadOnlyMany persistentVolumeReclaimPolicy: Retain hostPath: - path: "/home/ubuntu/fabric-samples/test-network" #Update to correct host paths where the MSP is located. + path: "/home/ubuntu/fabric-samples/test-network" #Update to correct host paths where the MSP is located. claimRef: name: dlt-pvc --- - apiVersion: v1 kind: Service metadata: @@ -194,7 +190,6 @@ spec: type: NodePort --- - apiVersion: v1 kind: Service metadata: -- GitLab From ff13999fd9317c803c8e1007fb00951148f3cba6 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 4 Sep 2024 14:20:50 +0000 Subject: [PATCH 73/94] Recovered legacy Gateway code --- src/dlt/gateway/legacy/.gitignore | 90 +++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/dlt/gateway/legacy/.gitignore diff --git a/src/dlt/gateway/legacy/.gitignore b/src/dlt/gateway/legacy/.gitignore new file mode 100644 index 000000000..9ecdb254c --- /dev/null +++ b/src/dlt/gateway/legacy/.gitignore @@ -0,0 +1,90 @@ +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# From https://github.com/github/gitignore/blob/master/Gradle.gitignore +/.gradle/ +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +local.properties +wallet*/ \ No newline at end of file -- GitLab From 63bc43337e3eaaa81239b3e0073b7f710cba05ef Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Mon, 9 Sep 2024 15:31:37 +0200 Subject: [PATCH 74/94] Code cleanup --- src/common/method_wrappers/Decorator.py | 14 - .../connector/client/DltConnectorClient.py | 70 +- ...ientSync.py => DltConnectorClientAsync.py} | 57 +- .../connector/client/DltEventsCollector.py | 4 +- src/dlt/connector/client/DltGatewayClient.py | 73 +- .../connector/client/DltGatewayClientAsync.py | 87 + .../connector/client/DltGatewayClientEvent.py | 57 - .../DltConnectorServiceServicerImpl.py | 18 +- src/dlt/connector/service/__main__.py | 6 +- .../event_dispatcher/DltEventDispatcher.py | 8 +- .../automation/k8sconfig/configmap.yaml | 17 - .../k8sconfig/deploy_dlt_gateway.sh | 34 - .../automation/k8sconfig/deployment.yaml | 74 - .../k8sconfig/persistent-volume-claim.yaml | 11 - .../k8sconfig/persistent-volume.yaml | 15 - .../k8sconfig/remove_dlt_gateway.sh | 37 - .../gateway/automation/k8sconfig/service.yaml | 14 - .../automation/k8sconfig/simpletest.yaml | 19 - .../{adrenalineTopo.go => chaincode.go} | 14 + src/dlt/gateway/chaincode/go.mod | 16 +- src/dlt/gateway/dltApp/proto/context.proto | 14 + src/dlt/gateway/dltApp/src/dltGateway.js | 15 + src/dlt/gateway/dltApp/src/fabricConnect.ts | 17 +- .../dltApp/tests/dltApp_Test/.gitignore | 1 - .../tests/dltApp_Test/package-lock.json | 2060 ----------------- .../dltApp/tests/dltApp_Test/package.json | 34 - .../dltApp_Test/src/adrenalineDLT_app.ts | 341 --- .../dltApp/tests/dltApp_Test/tsconfig.json | 18 - src/dlt/gateway/dltApp/tests/perfTest.js | 14 + src/dlt/gateway/dltApp/tests/rateTest.js | 14 + src/dlt/gateway/dltApp/tests/simpleTest.js | 14 + .../dltApp/{src => tests}/testEvents.js | 15 + .../dltApp/{src => tests}/testGateway.js | 15 + src/dlt/gateway/dltApp/tsconfig.json | 2 +- src/dlt/performance/__main__.py | 2 +- src/dlt/performance/play_ground/Dlt.py | 2 +- src/dlt/performance/play_ground/__init__.py | 2 +- .../service/InterdomainServiceServicerImpl.py | 4 +- .../topology_abstractor/DltRecordSender.py | 6 +- .../topology_abstractor/TopologyAbstractor.py | 2 +- src/load_generator/load_gen/DltTools.py | 2 +- .../load_gen/RequestGenerator.py | 2 +- .../tests/test_dlt_functional.py | 2 +- 43 files changed, 318 insertions(+), 2925 deletions(-) rename src/dlt/connector/client/{DltConnectorClientSync.py => DltConnectorClientAsync.py} (72%) create mode 100644 src/dlt/connector/client/DltGatewayClientAsync.py delete mode 100644 src/dlt/connector/client/DltGatewayClientEvent.py delete mode 100644 src/dlt/gateway/automation/k8sconfig/configmap.yaml delete mode 100644 src/dlt/gateway/automation/k8sconfig/deploy_dlt_gateway.sh delete mode 100644 src/dlt/gateway/automation/k8sconfig/deployment.yaml delete mode 100644 src/dlt/gateway/automation/k8sconfig/persistent-volume-claim.yaml delete mode 100644 src/dlt/gateway/automation/k8sconfig/persistent-volume.yaml delete mode 100644 src/dlt/gateway/automation/k8sconfig/remove_dlt_gateway.sh delete mode 100644 src/dlt/gateway/automation/k8sconfig/service.yaml delete mode 100644 src/dlt/gateway/automation/k8sconfig/simpletest.yaml rename src/dlt/gateway/chaincode/{adrenalineTopo.go => chaincode.go} (88%) delete mode 100644 src/dlt/gateway/dltApp/tests/dltApp_Test/.gitignore delete mode 100644 src/dlt/gateway/dltApp/tests/dltApp_Test/package-lock.json delete mode 100644 src/dlt/gateway/dltApp/tests/dltApp_Test/package.json delete mode 100644 src/dlt/gateway/dltApp/tests/dltApp_Test/src/adrenalineDLT_app.ts delete mode 100644 src/dlt/gateway/dltApp/tests/dltApp_Test/tsconfig.json rename src/dlt/gateway/dltApp/{src => tests}/testEvents.js (66%) rename src/dlt/gateway/dltApp/{src => tests}/testGateway.js (84%) diff --git a/src/common/method_wrappers/Decorator.py b/src/common/method_wrappers/Decorator.py index bfda31ec9..d86a769ef 100644 --- a/src/common/method_wrappers/Decorator.py +++ b/src/common/method_wrappers/Decorator.py @@ -12,20 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - import grpc, json, logging, threading from enum import Enum from prettytable import PrettyTable diff --git a/src/dlt/connector/client/DltConnectorClient.py b/src/dlt/connector/client/DltConnectorClient.py index a2224dd32..e383217d8 100644 --- a/src/dlt/connector/client/DltConnectorClient.py +++ b/src/dlt/connector/client/DltConnectorClient.py @@ -12,12 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# DltConnectorClient.py - -import grpc -import logging -import asyncio - +import grpc, logging from common.Constants import ServiceNameEnum from common.Settings import get_service_host, get_service_port_grpc from common.proto.context_pb2 import Empty, TopologyId @@ -40,90 +35,77 @@ class DltConnectorClient: LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) self.channel = None self.stub = None - #self.connect() - #LOGGER.debug('Channel created') + self.connect() + LOGGER.debug('Channel created') - async def connect(self): - self.channel = grpc.aio.insecure_channel(self.endpoint) + def connect(self): + self.channel = grpc.insecure_channel(self.endpoint) self.stub = DltConnectorServiceStub(self.channel) - LOGGER.debug('Channel created') - async def close(self): - if self.channel is not None: - await self.channel.close() + def close(self): + if self.channel is not None: self.channel.close() self.channel = None self.stub = None @RETRY_DECORATOR - async def RecordAll(self, request: TopologyId) -> Empty: + def RecordAll(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAll request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordAll(request) + response = self.stub.RecordAll(request) LOGGER.debug('RecordAll result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordAllDevices(self, request: TopologyId) -> Empty: + def RecordAllDevices(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAllDevices request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordAllDevices(request) + response = self.stub.RecordAllDevices(request) LOGGER.debug('RecordAllDevices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - # async def RecordDevice(self, request: DltDeviceId) -> Empty: - # LOGGER.debug('RECORD_DEVICE request received: {:s}'.format(grpc_message_to_json_string(request))) - - # Simulate some asynchronous processing delay - # await asyncio.sleep(2) # Simulates processing time - - # Create a dummy response (Empty message) - # response = Empty() - - # LOGGER.debug('RECORD_DEVICE processing complete for request: {:s}'.format(grpc_message_to_json_string(request))) - # return response - async def RecordDevice(self, request: DltDeviceId) -> Empty: - LOGGER.debug('RECORD_DEVICE request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordDevice(request) + def RecordDevice(self, request : DltDeviceId) -> Empty: + LOGGER.debug('RecordDevice request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.RecordDevice(request) LOGGER.debug('RecordDevice result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordAllLinks(self, request: TopologyId) -> Empty: + def RecordAllLinks(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAllLinks request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordAllLinks(request) + response = self.stub.RecordAllLinks(request) LOGGER.debug('RecordAllLinks result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordLink(self, request: DltLinkId) -> Empty: + def RecordLink(self, request : DltLinkId) -> Empty: LOGGER.debug('RecordLink request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordLink(request) + response = self.stub.RecordLink(request) LOGGER.debug('RecordLink result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordAllServices(self, request: TopologyId) -> Empty: + def RecordAllServices(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAllServices request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordAllServices(request) + response = self.stub.RecordAllServices(request) LOGGER.debug('RecordAllServices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordService(self, request: DltServiceId) -> Empty: + def RecordService(self, request : DltServiceId) -> Empty: LOGGER.debug('RecordService request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordService(request) + response = self.stub.RecordService(request) LOGGER.debug('RecordService result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordAllSlices(self, request: TopologyId) -> Empty: + def RecordAllSlices(self, request : TopologyId) -> Empty: LOGGER.debug('RecordAllSlices request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordAllSlices(request) + response = self.stub.RecordAllSlices(request) LOGGER.debug('RecordAllSlices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - async def RecordSlice(self, request: DltSliceId) -> Empty: + def RecordSlice(self, request : DltSliceId) -> Empty: LOGGER.debug('RecordSlice request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordSlice(request) + response = self.stub.RecordSlice(request) LOGGER.debug('RecordSlice result: {:s}'.format(grpc_message_to_json_string(response))) return response diff --git a/src/dlt/connector/client/DltConnectorClientSync.py b/src/dlt/connector/client/DltConnectorClientAsync.py similarity index 72% rename from src/dlt/connector/client/DltConnectorClientSync.py rename to src/dlt/connector/client/DltConnectorClientAsync.py index a633e89bd..23cd63fa0 100644 --- a/src/dlt/connector/client/DltConnectorClientSync.py +++ b/src/dlt/connector/client/DltConnectorClientAsync.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc, logging +import grpc, logging, asyncio + from common.Constants import ServiceNameEnum from common.Settings import get_service_host, get_service_port_grpc from common.proto.context_pb2 import Empty, TopologyId @@ -27,7 +28,7 @@ MAX_RETRIES = 15 DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') -class DltConnectorClientSync: +class DltConnectorClientAsync: def __init__(self, host=None, port=None): if not host: host = get_service_host(ServiceNameEnum.DLT) if not port: port = get_service_port_grpc(ServiceNameEnum.DLT) @@ -35,77 +36,79 @@ class DltConnectorClientSync: LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) self.channel = None self.stub = None - self.connect() - LOGGER.debug('Channel created') + #self.connect() + #LOGGER.debug('Channel created') - def connect(self): - self.channel = grpc.insecure_channel(self.endpoint) + async def connect(self): + self.channel = grpc.aio.insecure_channel(self.endpoint) self.stub = DltConnectorServiceStub(self.channel) + LOGGER.debug('Channel created') - def close(self): - if self.channel is not None: self.channel.close() + async def close(self): + if self.channel is not None: + await self.channel.close() self.channel = None self.stub = None @RETRY_DECORATOR - def RecordAll(self, request : TopologyId) -> Empty: + async def RecordAll(self, request: TopologyId) -> Empty: LOGGER.debug('RecordAll request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAll(request) + response = await self.stub.RecordAll(request) LOGGER.debug('RecordAll result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordAllDevices(self, request : TopologyId) -> Empty: + async def RecordAllDevices(self, request: TopologyId) -> Empty: LOGGER.debug('RecordAllDevices request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAllDevices(request) + response = await self.stub.RecordAllDevices(request) LOGGER.debug('RecordAllDevices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordDevice(self, request : DltDeviceId) -> Empty: - LOGGER.debug('RecordDevice request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordDevice(request) + async def RecordDevice(self, request: DltDeviceId) -> Empty: + LOGGER.debug('RECORD_DEVICE request: {:s}'.format(grpc_message_to_json_string(request))) + response = await self.stub.RecordDevice(request) LOGGER.debug('RecordDevice result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordAllLinks(self, request : TopologyId) -> Empty: + async def RecordAllLinks(self, request: TopologyId) -> Empty: LOGGER.debug('RecordAllLinks request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAllLinks(request) + response = await self.stub.RecordAllLinks(request) LOGGER.debug('RecordAllLinks result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordLink(self, request : DltLinkId) -> Empty: + async def RecordLink(self, request: DltLinkId) -> Empty: LOGGER.debug('RecordLink request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordLink(request) + response = await self.stub.RecordLink(request) LOGGER.debug('RecordLink result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordAllServices(self, request : TopologyId) -> Empty: + async def RecordAllServices(self, request: TopologyId) -> Empty: LOGGER.debug('RecordAllServices request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAllServices(request) + response = await self.stub.RecordAllServices(request) LOGGER.debug('RecordAllServices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordService(self, request : DltServiceId) -> Empty: + async def RecordService(self, request: DltServiceId) -> Empty: LOGGER.debug('RecordService request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordService(request) + response = await self.stub.RecordService(request) LOGGER.debug('RecordService result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordAllSlices(self, request : TopologyId) -> Empty: + async def RecordAllSlices(self, request: TopologyId) -> Empty: LOGGER.debug('RecordAllSlices request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordAllSlices(request) + response = await self.stub.RecordAllSlices(request) LOGGER.debug('RecordAllSlices result: {:s}'.format(grpc_message_to_json_string(response))) return response @RETRY_DECORATOR - def RecordSlice(self, request : DltSliceId) -> Empty: + async def RecordSlice(self, request: DltSliceId) -> Empty: LOGGER.debug('RecordSlice request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.RecordSlice(request) + response = await self.stub.RecordSlice(request) LOGGER.debug('RecordSlice result: {:s}'.format(grpc_message_to_json_string(response))) return response diff --git a/src/dlt/connector/client/DltEventsCollector.py b/src/dlt/connector/client/DltEventsCollector.py index 2e38d0445..594d84838 100644 --- a/src/dlt/connector/client/DltEventsCollector.py +++ b/src/dlt/connector/client/DltEventsCollector.py @@ -16,7 +16,7 @@ from typing import Callable, Optional import grpc, logging, queue, threading, time from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordSubscription from common.tools.grpc.Tools import grpc_message_to_json_string -from dlt.connector.client.DltGatewayClientEvent import DltGatewayClientEvent +from src.dlt.connector.client.DltGatewayClient import DltGatewayClient LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) @@ -31,7 +31,7 @@ LOGGER.setLevel(logging.DEBUG) class DltEventsCollector(threading.Thread): def __init__( - self, dltgateway_client : DltGatewayClientEvent, + self, dltgateway_client : DltGatewayClient, log_events_received : bool = False, event_handler : Optional[Callable[[DltRecordEvent], Optional[DltRecordEvent]]] = None, ) -> None: diff --git a/src/dlt/connector/client/DltGatewayClient.py b/src/dlt/connector/client/DltGatewayClient.py index 2690dfb66..71f336866 100644 --- a/src/dlt/connector/client/DltGatewayClient.py +++ b/src/dlt/connector/client/DltGatewayClient.py @@ -13,14 +13,9 @@ # limitations under the License. - -import grpc -import logging -import asyncio -from typing import Iterator, List -from common.proto.context_pb2 import Empty, TeraFlowController -from common.proto.dlt_gateway_pb2 import ( - DltPeerStatus, DltPeerStatusList, DltRecord, DltRecordEvent, DltRecordId, DltRecordStatus, DltRecordSubscription) +import grpc, logging +from typing import Iterator +from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordSubscription from common.proto.dlt_gateway_pb2_grpc import DltGatewayServiceStub from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.grpc.Tools import grpc_message_to_json_string @@ -40,70 +35,22 @@ class DltGatewayClient: LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) self.channel = None self.stub = None - #self.connect() - self.message_queue: List[DltRecord] = [] - #LOGGER.debug('Channel created') - + self.connect() + LOGGER.debug('Channel created') - async def connect(self): - self.channel = grpc.aio.insecure_channel(self.endpoint) + def connect(self): + self.channel = grpc.insecure_channel(self.endpoint) self.stub = DltGatewayServiceStub(self.channel) - LOGGER.debug('Channel created') - async def close(self): + def close(self): if self.channel is not None: - await self.channel.close() + self.channel.close() self.channel = None self.stub = None - # async def dummy_process(self, request: DltRecord): - # # Simulate processing delay - # await asyncio.sleep(2) - # return DltRecordStatus(status="DLTRECORDSTATUS_SUCCEEDED", record_id=request.record_id) - - - @RETRY_DECORATOR - async def RecordToDlt(self, request : DltRecord) -> DltRecordStatus: - LOGGER.debug('RecordToDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.RecordToDlt(request) - LOGGER.debug('RecordToDlt result: {:s}'.format(grpc_message_to_json_string(response))) - return response - - # @RETRY_DECORATOR - # async def RecordToDlt(self, request: DltRecord) -> DltRecordStatus: - # self.message_queue.append(request) - # LOGGER.debug(f'RecordToDlt request: {grpc_message_to_json_string(request)}') - # LOGGER.debug(f'Queue length before processing: {len(self.message_queue)}') - # response = await self.dummy_process(request) - # LOGGER.debug(f'RecordToDlt result: {grpc_message_to_json_string(response)}') - # self.message_queue.remove(request) - # LOGGER.debug(f'Queue length after processing: {len(self.message_queue)}') - # return response - @RETRY_DECORATOR - async def GetFromDlt(self, request : DltRecordId) -> DltRecord: - LOGGER.debug('GetFromDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.GetFromDlt(request) - LOGGER.debug('GetFromDlt result: {:s}'.format(grpc_message_to_json_string(response))) - return response - - @RETRY_DECORATOR - def SubscribeToDlt(self, request : DltRecordSubscription) -> Iterator[DltRecordEvent]: + def SubscribeToDlt(self, request: DltRecordSubscription) -> Iterator[DltRecordEvent]: LOGGER.debug('SubscribeToDlt request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.SubscribeToDlt(request) LOGGER.debug('SubscribeToDlt result: {:s}'.format(grpc_message_to_json_string(response))) return response - - @RETRY_DECORATOR - async def GetDltStatus(self, request : TeraFlowController) -> DltPeerStatus: - LOGGER.debug('GetDltStatus request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.GetDltStatus(request) - LOGGER.debug('GetDltStatus result: {:s}'.format(grpc_message_to_json_string(response))) - return response - - @RETRY_DECORATOR - async def GetDltPeers(self, request : Empty) -> DltPeerStatusList: - LOGGER.debug('GetDltPeers request: {:s}'.format(grpc_message_to_json_string(request))) - response = await self.stub.GetDltPeers(request) - LOGGER.debug('GetDltPeers result: {:s}'.format(grpc_message_to_json_string(response))) - return response diff --git a/src/dlt/connector/client/DltGatewayClientAsync.py b/src/dlt/connector/client/DltGatewayClientAsync.py new file mode 100644 index 000000000..84c753e03 --- /dev/null +++ b/src/dlt/connector/client/DltGatewayClientAsync.py @@ -0,0 +1,87 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import grpc, logging, asyncio +from typing import Iterator, List +from common.proto.context_pb2 import Empty, TeraFlowController +from common.proto.dlt_gateway_pb2 import ( + DltPeerStatus, DltPeerStatusList, DltRecord, DltRecordEvent, DltRecordId, DltRecordStatus, DltRecordSubscription) +from common.proto.dlt_gateway_pb2_grpc import DltGatewayServiceStub +from common.tools.client.RetryDecorator import retry, delay_exponential +from common.tools.grpc.Tools import grpc_message_to_json_string +from dlt.connector.Config import DLT_GATEWAY_HOST, DLT_GATEWAY_PORT + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +MAX_RETRIES = 15 +DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) +RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') + +class DltGatewayClientAsync: + def __init__(self, host=None, port=None): + if not host: host = DLT_GATEWAY_HOST + if not port: port = DLT_GATEWAY_PORT + self.endpoint = '{:s}:{:s}'.format(str(host), str(port)) + LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) + self.channel = None + self.stub = None + #self.connect() + self.message_queue: List[DltRecord] = [] + #LOGGER.debug('Channel created') + + async def connect(self): + self.channel = grpc.aio.insecure_channel(self.endpoint) + self.stub = DltGatewayServiceStub(self.channel) + LOGGER.debug('Channel created') + + async def close(self): + if self.channel is not None: + await self.channel.close() + self.channel = None + self.stub = None + + @RETRY_DECORATOR + async def RecordToDlt(self, request : DltRecord) -> DltRecordStatus: + LOGGER.debug('RecordToDlt request: {:s}'.format(grpc_message_to_json_string(request))) + response = await self.stub.RecordToDlt(request) + LOGGER.debug('RecordToDlt result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + async def GetFromDlt(self, request : DltRecordId) -> DltRecord: + LOGGER.debug('GetFromDlt request: {:s}'.format(grpc_message_to_json_string(request))) + response = await self.stub.GetFromDlt(request) + LOGGER.debug('GetFromDlt result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def SubscribeToDlt(self, request : DltRecordSubscription) -> Iterator[DltRecordEvent]: + LOGGER.debug('SubscribeToDlt request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.SubscribeToDlt(request) + LOGGER.debug('SubscribeToDlt result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + async def GetDltStatus(self, request : TeraFlowController) -> DltPeerStatus: + LOGGER.debug('GetDltStatus request: {:s}'.format(grpc_message_to_json_string(request))) + response = await self.stub.GetDltStatus(request) + LOGGER.debug('GetDltStatus result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + async def GetDltPeers(self, request : Empty) -> DltPeerStatusList: + LOGGER.debug('GetDltPeers request: {:s}'.format(grpc_message_to_json_string(request))) + response = await self.stub.GetDltPeers(request) + LOGGER.debug('GetDltPeers result: {:s}'.format(grpc_message_to_json_string(response))) + return response diff --git a/src/dlt/connector/client/DltGatewayClientEvent.py b/src/dlt/connector/client/DltGatewayClientEvent.py deleted file mode 100644 index 6cbaf1a27..000000000 --- a/src/dlt/connector/client/DltGatewayClientEvent.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import grpc -import logging -from typing import Iterator -from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordSubscription -from common.proto.dlt_gateway_pb2_grpc import DltGatewayServiceStub -from common.tools.client.RetryDecorator import retry, delay_exponential -from common.tools.grpc.Tools import grpc_message_to_json_string -from dlt.connector.Config import DLT_GATEWAY_HOST, DLT_GATEWAY_PORT - -LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) -MAX_RETRIES = 15 -DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) -RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') - -class DltGatewayClientEvent: - def __init__(self, host=None, port=None): - if not host: host = DLT_GATEWAY_HOST - if not port: port = DLT_GATEWAY_PORT - self.endpoint = '{:s}:{:s}'.format(str(host), str(port)) - LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) - self.channel = None - self.stub = None - self.connect() - LOGGER.debug('Channel created') - - def connect(self): - self.channel = grpc.insecure_channel(self.endpoint) - self.stub = DltGatewayServiceStub(self.channel) - - def close(self): - if self.channel is not None: - self.channel.close() - self.channel = None - self.stub = None - - @RETRY_DECORATOR - def SubscribeToDlt(self, request: DltRecordSubscription) -> Iterator[DltRecordEvent]: - LOGGER.debug('SubscribeToDlt request: {:s}'.format(grpc_message_to_json_string(request))) - response = self.stub.SubscribeToDlt(request) - LOGGER.debug('SubscribeToDlt result: {:s}'.format(grpc_message_to_json_string(response))) - return response diff --git a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py index 46c58e063..17cc6fd33 100644 --- a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py +++ b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py @@ -12,10 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc -import asyncio +import grpc, asyncio, logging from grpc.aio import ServicerContext -import logging from typing import Optional from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method_async from common.proto.context_pb2 import Empty, TopologyId @@ -24,7 +22,7 @@ from common.proto.dlt_connector_pb2_grpc import DltConnectorServiceServicer from common.proto.dlt_gateway_pb2 import DltRecord, DltRecordId, DltRecordOperationEnum, DltRecordTypeEnum from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient -from dlt.connector.client.DltGatewayClient import DltGatewayClient +from src.dlt.connector.client.DltGatewayClientAsync import DltGatewayClientAsync from .tools.Checkers import record_exists LOGGER = logging.getLogger(__name__) @@ -35,7 +33,7 @@ METRICS_POOL = MetricsPool('DltConnector', 'RPC') class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): def __init__(self): LOGGER.debug('Creating Servicer...') - self.dltgateway_client = DltGatewayClient() + self.dltgateway_client = DltGatewayClientAsync() LOGGER.debug('Servicer Created') async def initialize(self): @@ -50,16 +48,6 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): return Empty() @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) - # async def RecordDevice(self, request: DltDeviceId, context: ServicerContext) -> Empty: - # LOGGER.debug('Received RecordDevice request: {:s}'.format(grpc_message_to_json_string(request))) - # try: - # if not request.delete: - # LOGGER.debug('Processing RecordDevice request: {:s}'.format(grpc_message_to_json_string(request))) - # # Perform any dummy operation or simply log the request - # LOGGER.debug('Processed RecordDevice request: {:s}'.format(grpc_message_to_json_string(request))) - # except Exception as e: - # LOGGER.error(f"Error processing RecordDevice: {e}") - # return Empty() async def RecordDevice(self, request: DltDeviceId, context: ServicerContext) -> Empty: data_json = None LOGGER.debug('RECORD_DEVICE = {:s}'.format(grpc_message_to_json_string(request))) diff --git a/src/dlt/connector/service/__main__.py b/src/dlt/connector/service/__main__.py index 09f525ed4..f8296660e 100644 --- a/src/dlt/connector/service/__main__.py +++ b/src/dlt/connector/service/__main__.py @@ -13,11 +13,7 @@ # limitations under the License. -import logging -import signal -import sys -import threading -import asyncio +import logging, signal, sys, threading, asyncio from prometheus_client import start_http_server from common.Constants import ServiceNameEnum from common.Settings import ( diff --git a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py index 29e8c096d..4ae6fec54 100644 --- a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py +++ b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py @@ -26,9 +26,9 @@ from common.tools.grpc.Tools import grpc_message_to_json_string from common.tools.object_factory.Context import json_context_id from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient -from dlt.connector.client.DltConnectorClientSync import DltConnectorClientSync +from src.dlt.connector.client.DltConnectorClient import DltConnectorClient from dlt.connector.client.DltEventsCollector import DltEventsCollector -from dlt.connector.client.DltGatewayClientEvent import DltGatewayClientEvent +from src.dlt.connector.client.DltGatewayClient import DltGatewayClient from interdomain.client.InterdomainClient import InterdomainClient LOGGER = logging.getLogger(__name__) @@ -40,8 +40,8 @@ ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) class Clients: def __init__(self) -> None: self.context_client = ContextClient() - self.dlt_connector_client = DltConnectorClientSync() - self.dlt_gateway_client = DltGatewayClientEvent() + self.dlt_connector_client = DltConnectorClient() + self.dlt_gateway_client = DltGatewayClient() self.interdomain_client = InterdomainClient() def close(self) -> None: diff --git a/src/dlt/gateway/automation/k8sconfig/configmap.yaml b/src/dlt/gateway/automation/k8sconfig/configmap.yaml deleted file mode 100644 index f8e19b798..000000000 --- a/src/dlt/gateway/automation/k8sconfig/configmap.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: dlt-config - namespace: dlt -#NEED to find a better way to setup ENV variables for Kubernetes services -##Modify the PEER_ENDPOINT IP according to your deployment -data: - CHANNEL_NAME: "channel1" - CHAINCODE_NAME: "adrenalineDLT" - MSP_ID: "Org1MSP" - PEER_ENDPOINT: "PEER_IP:PORT" #USE THE IP address and Ports of your Fabric deployment peers. - PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" - CRYPTO_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com" - KEY_DIRECTORY_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/keystore" - CERT_DIRECTORY_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/signcerts" - TLS_CERT_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com/peers/peer0.org1.adrenaline.com/tls/ca.crt" diff --git a/src/dlt/gateway/automation/k8sconfig/deploy_dlt_gateway.sh b/src/dlt/gateway/automation/k8sconfig/deploy_dlt_gateway.sh deleted file mode 100644 index 736d65ee8..000000000 --- a/src/dlt/gateway/automation/k8sconfig/deploy_dlt_gateway.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -# Namespace -NAMESPACE="dlt" - -# Configurations -CONFIGMAP="configmap.yaml" -PV="persistent-volume.yaml" -PVC="persistent-volume-claim.yaml" -DEPLOYMENT="deployment.yaml" -SERVICE="service.yaml" - -# Apply Configurations -echo "Applying ConfigMap..." -kubectl apply -f $CONFIGMAP - -echo "Applying PersistentVolume..." -kubectl apply -f $PV - -echo "Applying PersistentVolumeClaim..." -kubectl apply -f $PVC - -echo "Applying Deployment..." -kubectl apply -f $DEPLOYMENT - -echo "Applying Service..." -kubectl apply -f $SERVICE - -# Verify Deployment -echo "Verifying Deployment..." -kubectl get pods -n $NAMESPACE -kubectl get services -n $NAMESPACE - -echo "Deployment Completed." diff --git a/src/dlt/gateway/automation/k8sconfig/deployment.yaml b/src/dlt/gateway/automation/k8sconfig/deployment.yaml deleted file mode 100644 index 88ca9a5af..000000000 --- a/src/dlt/gateway/automation/k8sconfig/deployment.yaml +++ /dev/null @@ -1,74 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: dlt-gateway - namespace: dlt -spec: - replicas: 3 - selector: - matchLabels: - app: dlt-gateway - template: - metadata: - labels: - app: dlt-gateway - spec: - containers: - - name: dlt-gateway - image: shaifvier/dltgateway:v1.0.0 - ports: - - containerPort: 50051 - volumeMounts: - - mountPath: /test-network - name: dlt-volume - readOnly: true # Mount the volume as read-only - env: - - name: CHANNEL_NAME - valueFrom: - configMapKeyRef: - name: dlt-config - key: CHANNEL_NAME - - name: CHAINCODE_NAME - valueFrom: - configMapKeyRef: - name: dlt-config - key: CHAINCODE_NAME - - name: MSP_ID - valueFrom: - configMapKeyRef: - name: dlt-config - key: MSP_ID - - name: PEER_ENDPOINT - valueFrom: - configMapKeyRef: - name: dlt-config - key: PEER_ENDPOINT - - name: PEER_HOST_ALIAS - valueFrom: - configMapKeyRef: - name: dlt-config - key: PEER_HOST_ALIAS - - name: CRYPTO_PATH - valueFrom: - configMapKeyRef: - name: dlt-config - key: CRYPTO_PATH - - name: KEY_DIRECTORY_PATH - valueFrom: - configMapKeyRef: - name: dlt-config - key: KEY_DIRECTORY_PATH - - name: CERT_DIRECTORY_PATH - valueFrom: - configMapKeyRef: - name: dlt-config - key: CERT_DIRECTORY_PATH - - name: TLS_CERT_PATH - valueFrom: - configMapKeyRef: - name: dlt-config - key: TLS_CERT_PATH - volumes: - - name: dlt-volume - persistentVolumeClaim: - claimName: dlt-pvc diff --git a/src/dlt/gateway/automation/k8sconfig/persistent-volume-claim.yaml b/src/dlt/gateway/automation/k8sconfig/persistent-volume-claim.yaml deleted file mode 100644 index a1e4f477c..000000000 --- a/src/dlt/gateway/automation/k8sconfig/persistent-volume-claim.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: dlt-pvc - namespace: dlt -spec: - accessModes: - - ReadOnlyMany - resources: - requests: - storage: 1Gi diff --git a/src/dlt/gateway/automation/k8sconfig/persistent-volume.yaml b/src/dlt/gateway/automation/k8sconfig/persistent-volume.yaml deleted file mode 100644 index 435b02a25..000000000 --- a/src/dlt/gateway/automation/k8sconfig/persistent-volume.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: PersistentVolume -metadata: - name: dlt-pv -spec: - capacity: - storage: 1Gi - accessModes: - - ReadOnlyMany - persistentVolumeReclaimPolicy: Retain - hostPath: - path: "/home/cttc/test-network" # Update this path to the actual path on the host machine where the Kubernetes Pod will be deployed. - claimRef: - namespace: dlt - name: dlt-pvc \ No newline at end of file diff --git a/src/dlt/gateway/automation/k8sconfig/remove_dlt_gateway.sh b/src/dlt/gateway/automation/k8sconfig/remove_dlt_gateway.sh deleted file mode 100644 index 5633789f3..000000000 --- a/src/dlt/gateway/automation/k8sconfig/remove_dlt_gateway.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -# Namespace -NAMESPACE="dlt" - -# Configurations -CONFIGMAP="configmap.yaml" -PV="persistent-volume.yaml" -PVC="persistent-volume-claim.yaml" -DEPLOYMENT="deployment.yaml" -SERVICE="service.yaml" - -# Delete Configurations -echo "Deleting Deployment..." -kubectl delete -f $DEPLOYMENT || echo "Deployment not found." - -echo "Deleting Service..." -kubectl delete -f $SERVICE || echo "Service not found." - -echo "Deleting PersistentVolumeClaim..." -kubectl delete -f $PVC || echo "PersistentVolumeClaim not found." - -echo "Deleting PersistentVolume..." -kubectl delete -f $PV || echo "PersistentVolume not found." - -echo "Deleting ConfigMap..." -kubectl delete -f $CONFIGMAP || echo "ConfigMap not found." - -# Verify Deletion -echo "Verifying Deletion..." -kubectl get pods -n $NAMESPACE -kubectl get services -n $NAMESPACE -kubectl get pvc -n $NAMESPACE -kubectl get pv -kubectl get configmap -n $NAMESPACE - -echo "Deletion Completed." diff --git a/src/dlt/gateway/automation/k8sconfig/service.yaml b/src/dlt/gateway/automation/k8sconfig/service.yaml deleted file mode 100644 index 2590e99c9..000000000 --- a/src/dlt/gateway/automation/k8sconfig/service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: dlt-gateway - namespace: dlt -spec: - selector: - app: dlt-gateway - ports: - - protocol: TCP - port: 50051 # External port - targetPort: 50051 # Internal port of the service - nodePort: 32001 # A high port number for external access - type: NodePort diff --git a/src/dlt/gateway/automation/k8sconfig/simpletest.yaml b/src/dlt/gateway/automation/k8sconfig/simpletest.yaml deleted file mode 100644 index 0926285b8..000000000 --- a/src/dlt/gateway/automation/k8sconfig/simpletest.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: dlt-gateway - namespace: dlt -spec: - containers: - - name: test-container - image: busybox - command: ["sh", "-c", "sleep 3600"] # Keep the container running for testing - volumeMounts: - - mountPath: /mnt/test-network - name: dlt-volume - readOnly: true - volumes: - - name: dlt-volume - hostPath: - path: /home/cttc/test-network - type: Directory diff --git a/src/dlt/gateway/chaincode/adrenalineTopo.go b/src/dlt/gateway/chaincode/chaincode.go similarity index 88% rename from src/dlt/gateway/chaincode/adrenalineTopo.go rename to src/dlt/gateway/chaincode/chaincode.go index 4052d7c2c..878cbe942 100644 --- a/src/dlt/gateway/chaincode/adrenalineTopo.go +++ b/src/dlt/gateway/chaincode/chaincode.go @@ -1,3 +1,17 @@ +// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( diff --git a/src/dlt/gateway/chaincode/go.mod b/src/dlt/gateway/chaincode/go.mod index d6c9e7a40..27f86fbab 100644 --- a/src/dlt/gateway/chaincode/go.mod +++ b/src/dlt/gateway/chaincode/go.mod @@ -1,4 +1,18 @@ -module adrenalineTopo + + +module chaincode go 1.17 diff --git a/src/dlt/gateway/dltApp/proto/context.proto b/src/dlt/gateway/dltApp/proto/context.proto index 88ec0605d..dccb72dfa 100644 --- a/src/dlt/gateway/dltApp/proto/context.proto +++ b/src/dlt/gateway/dltApp/proto/context.proto @@ -1,3 +1,17 @@ +// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto3"; package context; diff --git a/src/dlt/gateway/dltApp/src/dltGateway.js b/src/dlt/gateway/dltApp/src/dltGateway.js index ba9a67a31..374be6464 100644 --- a/src/dlt/gateway/dltApp/src/dltGateway.js +++ b/src/dlt/gateway/dltApp/src/dltGateway.js @@ -1,3 +1,18 @@ +// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const path = require('path'); diff --git a/src/dlt/gateway/dltApp/src/fabricConnect.ts b/src/dlt/gateway/dltApp/src/fabricConnect.ts index cab955958..4bef9b335 100644 --- a/src/dlt/gateway/dltApp/src/fabricConnect.ts +++ b/src/dlt/gateway/dltApp/src/fabricConnect.ts @@ -1,7 +1,16 @@ -/* - * - * SPDX-License-Identifier: Apache-2.0 - */ +// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. import * as grpc from '@grpc/grpc-js'; import { connect, Contract, Identity, Signer, signers, Network, CloseableAsyncIterable, ChaincodeEvent, GatewayError } from '@hyperledger/fabric-gateway'; diff --git a/src/dlt/gateway/dltApp/tests/dltApp_Test/.gitignore b/src/dlt/gateway/dltApp/tests/dltApp_Test/.gitignore deleted file mode 100644 index 40b878db5..000000000 --- a/src/dlt/gateway/dltApp/tests/dltApp_Test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ \ No newline at end of file diff --git a/src/dlt/gateway/dltApp/tests/dltApp_Test/package-lock.json b/src/dlt/gateway/dltApp/tests/dltApp_Test/package-lock.json deleted file mode 100644 index c7e3f7976..000000000 --- a/src/dlt/gateway/dltApp/tests/dltApp_Test/package-lock.json +++ /dev/null @@ -1,2060 +0,0 @@ -{ - "name": "adrenalineDLT_APP", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "adrenalineDLT_APP", - "version": "1.0.0", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.9.7", - "@hyperledger/fabric-gateway": "~1.4.0", - "dotenv": "^16.4.5" - }, - "devDependencies": { - "@tsconfig/node18": "^18.2.2", - "@types/node": "^18.18.6", - "@typescript-eslint/eslint-plugin": "^6.9.0", - "@typescript-eslint/parser": "^6.9.0", - "eslint": "^8.52.0", - "typescript": "~5.2.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@grpc/grpc-js": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.7.tgz", - "integrity": "sha512-ZMBVjSeDAz3tFSehyO6Pd08xZT1HfIwq3opbeM4cDlBh52gmwp0wVIPcQur53NN0ac68HMZ/7SF2rGRD5KmVmg==", - "dependencies": { - "@grpc/proto-loader": "^0.7.13", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", - "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true - }, - "node_modules/@hyperledger/fabric-gateway": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@hyperledger/fabric-gateway/-/fabric-gateway-1.4.0.tgz", - "integrity": "sha512-dJ0eJdGBo8wtZ/oR5mADHnllp+pSuVOI7uq5fRFf0NTVk1SzlX42Q3kt4j53bJQaxd21TMvofgXNO+BCgJcB/A==", - "dependencies": { - "@grpc/grpc-js": "^1.9.0", - "@hyperledger/fabric-protos": "^0.2.0", - "asn1.js": "^5.4.1", - "bn.js": "^5.2.1", - "elliptic": "^6.5.4", - "google-protobuf": "^3.21.0" - }, - "engines": { - "node": ">=18.12.0" - }, - "optionalDependencies": { - "pkcs11js": "^1.3.0" - } - }, - "node_modules/@hyperledger/fabric-protos": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@hyperledger/fabric-protos/-/fabric-protos-0.2.1.tgz", - "integrity": "sha512-qjm0vIQIfCall804tWDeA8p/mUfu14sl5Sj+PbOn2yDKJq+7ThoIhNsLAqf+BCxUfqsoqQq6AojhqQeTFyOOqg==", - "dependencies": { - "@grpc/grpc-js": "^1.9.0", - "google-protobuf": "^3.21.0" - }, - "engines": { - "node": ">=14.15.0" - } - }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "node_modules/@tsconfig/node18": { - "version": "18.2.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.4.tgz", - "integrity": "sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.19.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.32.tgz", - "integrity": "sha512-2bkg93YBSDKk8DLmmHnmj/Rwr18TLx7/n+I23BigFwgexUJoMHZOd8X1OFxuF/W3NN0S2W2E5sVabI5CPinNvA==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/elliptic": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", - "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/google-protobuf": { - "version": "3.21.2", - "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz", - "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" - }, - "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nan": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", - "optional": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkcs11js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkcs11js/-/pkcs11js-1.3.1.tgz", - "integrity": "sha512-eo7fTeQwYGzX1pFmRaf4ji/WcDW2XKpwqylOwzutsjNWECv6G9PzDHj3Yj5dX9EW/fydMnJG8xvWj/btnQT9TA==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "nan": "^2.15.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/PeculiarVentures" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/protobufjs": { - "version": "7.2.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", - "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", - "dev": true, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/src/dlt/gateway/dltApp/tests/dltApp_Test/package.json b/src/dlt/gateway/dltApp/tests/dltApp_Test/package.json deleted file mode 100644 index 228dbaad9..000000000 --- a/src/dlt/gateway/dltApp/tests/dltApp_Test/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "adrenalineDLT_APP", - "version": "1.0.0", - "description": "A DLT application that record and manages topology data as JSON implemented in typeScript using fabric-gateway", - "main": "dist/index.js", - "typings": "dist/index.d.ts", - "engines": { - "node": ">=18" - }, - "scripts": { - "build": "tsc", - "build:watch": "tsc -w", - "lint": "eslint . --ext .ts", - "prepare": "npm run build", - "pretest": "npm run lint", - "start": "node dist/adrenalineDLT_app.js" - }, - "engineStrict": true, - "author": "CTTC-Javier", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.9.7", - "@hyperledger/fabric-gateway": "~1.4.0", - "dotenv": "^16.4.5" - }, - "devDependencies": { - "@tsconfig/node18": "^18.2.2", - "@types/node": "^18.18.6", - "@typescript-eslint/eslint-plugin": "^6.9.0", - "@typescript-eslint/parser": "^6.9.0", - "eslint": "^8.52.0", - "typescript": "~5.2.2" - } -} diff --git a/src/dlt/gateway/dltApp/tests/dltApp_Test/src/adrenalineDLT_app.ts b/src/dlt/gateway/dltApp/tests/dltApp_Test/src/adrenalineDLT_app.ts deleted file mode 100644 index d1cd3a5e3..000000000 --- a/src/dlt/gateway/dltApp/tests/dltApp_Test/src/adrenalineDLT_app.ts +++ /dev/null @@ -1,341 +0,0 @@ -/* - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as grpc from '@grpc/grpc-js'; -import { connect, Contract, Identity, Signer, signers, Network, CloseableAsyncIterable, ChaincodeEvent, GatewayError } from '@hyperledger/fabric-gateway'; -import * as crypto from 'crypto'; -import { promises as fs } from 'fs'; -import * as path from 'path'; -import { TextDecoder } from 'util'; -import * as dotenv from 'dotenv'; - -dotenv.config(); -const channelName = envOrDefault('CHANNEL_NAME', 'channel1'); -const chaincodeName = envOrDefault('CHAINCODE_NAME', 'adrenalineDLT'); -const mspId = envOrDefault('MSP_ID', 'Org1MSP'); - -// Path to crypto materials. -const cryptoPath = envOrDefault('CRYPTO_PATH', path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.adrenaline.com')); - -// Path to user private key directory. -const keyDirectoryPath = envOrDefault('KEY_DIRECTORY_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.adrenaline.com', 'msp', 'keystore')); - -// Path to user certificate directory. -const certDirectoryPath = envOrDefault('CERT_DIRECTORY_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.adrenaline.com', 'msp', 'signcerts')); - -// Path to peer tls certificate. -const tlsCertPath = envOrDefault('TLS_CERT_PATH', path.resolve(cryptoPath, 'peers', 'peer0.org1.adrenaline.com', 'tls', 'ca.crt')); - -// Gateway peer endpoint. -const peerEndpoint = envOrDefault('PEER_ENDPOINT', 'localhost:7051'); - -// Gateway peer SSL host name override. -const peerHostAlias = envOrDefault('PEER_HOST_ALIAS', 'peer0.org1.adrenaline.com'); - -const utf8Decoder = new TextDecoder(); -const assetId = `asset${Date.now()}`; - -async function main(): Promise { - - await displayInputParameters(); - - // The gRPC client connection should be shared by all Gateway connections to this endpoint. - const client = await newGrpcConnection(); - - const gateway = connect({ - client, - identity: await newIdentity(), - signer: await newSigner(), - // Default timeouts for different gRPC calls - evaluateOptions: () => { - return { deadline: Date.now() + 5000 }; // 5 seconds - }, - endorseOptions: () => { - return { deadline: Date.now() + 15000 }; // 15 seconds - }, - submitOptions: () => { - return { deadline: Date.now() + 5000 }; // 5 seconds - }, - commitStatusOptions: () => { - return { deadline: Date.now() + 60000 }; // 1 minute - }, - }); - - let events: CloseableAsyncIterable | undefined; - - try { - // Get a network instance representing the channel where the smart contract is deployed. - const network = gateway.getNetwork(channelName); - - // Get the smart contract from the network. - const contract = network.getContract(chaincodeName); - - //Listen for events emitted by transactions - events = await startEventListening(network); - - // Initialize the ledger. - await initLedger(contract); - -// Create a new asset on the ledger and gets the blocknumber. - const firstBlockNumber = await StoreTopoData(contract); - - // Get the asset details by key. - await RetrieveTopoData(contract); - - //Updates existing record of a topology - await updateRecord(contract); - - // Verifies the changes. - await RetrieveTopoData(contract); - - //Deletes existing topology - await deleteRecordByID(contract); - - // Return all the current assets on the ledger. - await GetAllInfo(contract); - - // Update an asset which does not exist. - await updateNonExistentRecord(contract) - - // Replay events from the block containing the first transactions - await replayChaincodeEvents(network, firstBlockNumber); - - } finally { - events?.close(); - gateway.close(); - client.close(); - } -} - -main().catch(error => { - console.error('******** FAILED to run the application:', error); - process.exitCode = 1; -}); - -async function newGrpcConnection(): Promise { - const tlsRootCert = await fs.readFile(tlsCertPath); - const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); - return new grpc.Client(peerEndpoint, tlsCredentials, { - 'grpc.ssl_target_name_override': peerHostAlias, - }); -} - -async function newIdentity(): Promise { - const certPath = await getFirstDirFileName(certDirectoryPath); - const credentials = await fs.readFile(certPath); - return { mspId, credentials }; -} - -async function getFirstDirFileName(dirPath: string): Promise { - const files = await fs.readdir(dirPath); - return path.join(dirPath, files[0]); -} - -async function newSigner(): Promise { - const keyPath = await getFirstDirFileName(keyDirectoryPath); - const privateKeyPem = await fs.readFile(keyPath); - const privateKey = crypto.createPrivateKey(privateKeyPem); - return signers.newPrivateKeySigner(privateKey); -} - -/** - * This type of transaction would typically only be run once by an application the first time it was started after its - * initial deployment. A new version of the chaincode deployed later would likely not need to run an "init" function. - */ -async function initLedger(contract: Contract): Promise { - try { - console.log('\n--> Submit Transaction: InitLedger, function activates the chaincode'); - - await contract.submitTransaction('InitLedger'); - - console.log('*** Transaction committed successfully'); - } catch (error) { - console.error('Failed to submit InitLedger transaction:', error); - throw error; - } -} - - - -/** - * Evaluate a transaction to query ledger state. - */ -async function GetAllInfo(contract: Contract): Promise { - console.log('\n--> Evaluate Transaction: GetAllInfo, function returns all the current topologies on the ledger'); - - const resultBytes = await contract.evaluateTransaction('GetAllInfo'); - - const resultJson = utf8Decoder.decode(resultBytes); - const result = JSON.parse(resultJson); - console.log('*** Result:', result); -} - -/** - * Submit a transaction Asynchronously. - */ -async function StoreTopoData(contract: Contract): Promise { - console.log('\n--> Submit Transaction: StoreTopoData, records a new topology structure by Key values'); - - const storeTopoDataPath = path.resolve(__dirname, '..', '..', '..', 'samples', 'sampleTopo.json'); - const jsonData = await fs.readFile(storeTopoDataPath, 'utf8'); - const result = await contract.submitAsync('StoreTopoData', { - arguments: [assetId, jsonData] - }); - - const status = await result.getStatus(); - if (!status.successful) { - throw new Error(`failed to commit transaction ${status.transactionId} with status code ${status.code}`); - } - - console.log('*** Transaction committed successfully'); - return status.blockNumber; -} - -/** - * updateRecord(), updates topology record by key synchronously. - */ -async function updateRecord(contract: Contract): Promise { - console.log(`\n--> Submit transaction: UpdateTopoData, ${assetId}`); - - - try { - const updateTopoDataPath = path.resolve(__dirname, '..', '..', '..', 'samples', 'updatedTopo.json'); - const jsonData = await fs.readFile(updateTopoDataPath, 'utf8'); - await contract.submitTransaction('UpdateTopoData', assetId, jsonData); - console.log('UpdateTopoData committed successfully'); - } catch (error) { - console.log('*** Successfully caught the error: \n', error); - } -} - -/** - * RetrieveTopoData(), gets the topology information by key. - */ -async function RetrieveTopoData(contract: Contract): Promise { - console.log('\n--> Evaluate Transaction: RetrieveTopoData, function returns topology attributes'); - - const resultBytes = await contract.evaluateTransaction('RetrieveTopoData', assetId); - - const resultJson = utf8Decoder.decode(resultBytes); - const result = JSON.parse(resultJson); - console.log('*** Result:', result); -} - -/** - * submitTransaction() will throw an error containing details of any error responses from the smart contract. - */ -async function updateNonExistentRecord(contract: Contract): Promise{ - console.log('\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error'); - - try { - const updateTopoDataPath = path.resolve(__dirname, '..', '..', '..', 'samples', 'updatedTopo.json'); - const jsonData = await fs.readFile(updateTopoDataPath, 'utf8'); - await contract.submitTransaction('UpdateTopoData', 'nonExist', jsonData) - console.log('******** FAILED to return an error'); - } catch (error) { - console.log('*** Successfully caught the error: \n', error); - } -} - -/** - * deleteRecordByID() removes a record from the ledger. - */ - -async function deleteRecordByID(contract: Contract): Promise{ - console.log(`\n--> Submit transaction: deleteRecordByID, ${assetId}`); - try { - - await contract.submitTransaction('DeleteTopo', assetId); - console.log('\n*** deleteRecordByID committed successfully'); - } catch (error) { - console.log('*** Successfully caught the error: \n', error); - } -} - - -/** - * envOrDefault() will return the value of an environment variable, or a default value if the variable is undefined. - */ -function envOrDefault(key: string, defaultValue: string): string { - return process.env[key] || defaultValue; -} - -/** - * displayInputParameters() will print the global scope parameters used by the main driver routine. - */ -async function displayInputParameters(): Promise { - console.log(`channelName: ${channelName}`); - console.log(`chaincodeName: ${chaincodeName}`); - console.log(`mspId: ${mspId}`); - console.log(`cryptoPath: ${cryptoPath}`); - console.log(`keyDirectoryPath: ${keyDirectoryPath}`); - console.log(`certDirectoryPath: ${certDirectoryPath}`); - console.log(`tlsCertPath: ${tlsCertPath}`); - console.log(`peerEndpoint: ${peerEndpoint}`); - console.log(`peerHostAlias: ${peerHostAlias}`); -} - -/** - * startEventListening() will initiate the event listener for chaincode events. - */ -async function startEventListening(network: Network): Promise> { - console.log('\n*** Start chaincode event listening'); - - const events = await network.getChaincodeEvents(chaincodeName); - - void readEvents(events); // Don't await - run asynchronously - return events; -} - -/** - * readEvents() format and display the events as a JSON. - */ -async function readEvents(events: CloseableAsyncIterable): Promise { - try { - for await (const event of events) { - const payload = parseJson(event.payload); - console.log(`\n<-- Chaincode event received: ${event.eventName} -`, payload); - } - } catch (error: unknown) { - // Ignore the read error when events.close() is called explicitly - if (!(error instanceof GatewayError) || error.code !== grpc.status.CANCELLED.valueOf()) { - throw error; - } - } -} - -/** - * parseJson() formats a JSON. - */ -function parseJson(jsonBytes: Uint8Array): unknown { - const json = utf8Decoder.decode(jsonBytes); - return JSON.parse(json); -} - - -/** - * replayChaincodeEvents() - */ -async function replayChaincodeEvents(network: Network, startBlock: bigint): Promise { - console.log('\n*** Start chaincode event replay'); - - const events = await network.getChaincodeEvents(chaincodeName, { - startBlock, - }); - - try { - for await (const event of events) { - const payload = parseJson(event.payload); - console.log(`\n<-- Chaincode event replayed: ${event.eventName} -`, payload); - - if (event.eventName === 'DeleteTopo') { - // Reached the last submitted transaction so break to stop listening for events - break; - } - } - } finally { - events.close(); - } -} \ No newline at end of file diff --git a/src/dlt/gateway/dltApp/tests/dltApp_Test/tsconfig.json b/src/dlt/gateway/dltApp/tests/dltApp_Test/tsconfig.json deleted file mode 100644 index ed2150983..000000000 --- a/src/dlt/gateway/dltApp/tests/dltApp_Test/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends":"@tsconfig/node18/tsconfig.json", - "compilerOptions": { - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "outDir": "dist", - "declaration": true, - "sourceMap": true, - "noImplicitAny": true - }, - "include": [ - "./src/**/*" - ], - "exclude": [ - "./src/**/*.spec.ts" - ] - } - \ No newline at end of file diff --git a/src/dlt/gateway/dltApp/tests/perfTest.js b/src/dlt/gateway/dltApp/tests/perfTest.js index d2892d451..6549d4e7b 100644 --- a/src/dlt/gateway/dltApp/tests/perfTest.js +++ b/src/dlt/gateway/dltApp/tests/perfTest.js @@ -1,3 +1,17 @@ +// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + const { connectToNetwork } = require('../dist/fabricConnect'); const fsp = require('fs').promises; const fs = require('fs'); diff --git a/src/dlt/gateway/dltApp/tests/rateTest.js b/src/dlt/gateway/dltApp/tests/rateTest.js index 95370f487..ca3f540d3 100644 --- a/src/dlt/gateway/dltApp/tests/rateTest.js +++ b/src/dlt/gateway/dltApp/tests/rateTest.js @@ -1,3 +1,17 @@ +// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + const { connectToNetwork } = require('../dist/fabricConnect'); const fs = require('fs'); const util = require('util'); diff --git a/src/dlt/gateway/dltApp/tests/simpleTest.js b/src/dlt/gateway/dltApp/tests/simpleTest.js index 98c6ae135..d84b5dba1 100644 --- a/src/dlt/gateway/dltApp/tests/simpleTest.js +++ b/src/dlt/gateway/dltApp/tests/simpleTest.js @@ -1,3 +1,17 @@ +// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + const { connectToNetwork } = require('../dist/fabricConnect'); const fs = require('fs'); const util = require('util'); diff --git a/src/dlt/gateway/dltApp/src/testEvents.js b/src/dlt/gateway/dltApp/tests/testEvents.js similarity index 66% rename from src/dlt/gateway/dltApp/src/testEvents.js rename to src/dlt/gateway/dltApp/tests/testEvents.js index 68573e271..2590d40d4 100644 --- a/src/dlt/gateway/dltApp/src/testEvents.js +++ b/src/dlt/gateway/dltApp/tests/testEvents.js @@ -1,3 +1,18 @@ +// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const path = require('path'); diff --git a/src/dlt/gateway/dltApp/src/testGateway.js b/src/dlt/gateway/dltApp/tests/testGateway.js similarity index 84% rename from src/dlt/gateway/dltApp/src/testGateway.js rename to src/dlt/gateway/dltApp/tests/testGateway.js index 3486cb4f3..778c0f909 100644 --- a/src/dlt/gateway/dltApp/src/testGateway.js +++ b/src/dlt/gateway/dltApp/tests/testGateway.js @@ -1,3 +1,18 @@ +// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const path = require('path'); diff --git a/src/dlt/gateway/dltApp/tsconfig.json b/src/dlt/gateway/dltApp/tsconfig.json index 56f092a36..c81e4d5f9 100644 --- a/src/dlt/gateway/dltApp/tsconfig.json +++ b/src/dlt/gateway/dltApp/tsconfig.json @@ -10,7 +10,7 @@ }, "include": [ "./src/**/*" -, "src.old/grpcClient.js", "src.old/grpcServer.js" ], +, "src.old/grpcClient.js", "src.old/grpcServer.js", "tests/testEvents.js", "tests/testGateway.js" ], "exclude": [ "./src/**/*.spec.ts" ] diff --git a/src/dlt/performance/__main__.py b/src/dlt/performance/__main__.py index 1d6c41965..09248c1b1 100644 --- a/src/dlt/performance/__main__.py +++ b/src/dlt/performance/__main__.py @@ -14,7 +14,7 @@ import functools, logging, pathlib, sys, time from common.proto.dlt_gateway_pb2 import DltRecordEvent -from dlt.connector.client.DltGatewayClient import DltGatewayClient +from src.dlt.connector.client.DltGatewayClient import DltGatewayClient from dlt.connector.client.DltEventsCollector import DltEventsCollector from .play_ground.Enums import CONTEXT_EVENT_TYPE_TO_ACTION, RECORD_TYPE_TO_ENUM from .play_ground import PlayGround diff --git a/src/dlt/performance/play_ground/Dlt.py b/src/dlt/performance/play_ground/Dlt.py index b2c889545..5de186c8c 100644 --- a/src/dlt/performance/play_ground/Dlt.py +++ b/src/dlt/performance/play_ground/Dlt.py @@ -18,7 +18,7 @@ from common.proto.context_pb2 import Device, Link, Service, Slice from common.proto.dlt_gateway_pb2 import ( DltRecord, DltRecordId, DltRecordOperationEnum, DltRecordStatus, DltRecordTypeEnum) from common.tools.grpc.Tools import grpc_message_to_json_string -from dlt.connector.client.DltGatewayClient import DltGatewayClient +from src.dlt.connector.client.DltGatewayClient import DltGatewayClient from .PerfPoint import PerfPoint DLT_OPERATION_CREATE = DltRecordOperationEnum.DLTRECORDOPERATION_ADD diff --git a/src/dlt/performance/play_ground/__init__.py b/src/dlt/performance/play_ground/__init__.py index 68ad14dee..ce34d30a4 100644 --- a/src/dlt/performance/play_ground/__init__.py +++ b/src/dlt/performance/play_ground/__init__.py @@ -15,7 +15,7 @@ import logging, operator, random from typing import Dict, Tuple from common.proto.context_pb2 import Device, Link, Service, Slice -from dlt.connector.client.DltGatewayClient import DltGatewayClient +from src.dlt.connector.client.DltGatewayClient import DltGatewayClient from .Enums import ActionEnum, RecordTypeEnum from .Dlt import ( DLT_OPERATION_CREATE, DLT_OPERATION_DELETE, DLT_OPERATION_UPDATE, diff --git a/src/interdomain/service/InterdomainServiceServicerImpl.py b/src/interdomain/service/InterdomainServiceServicerImpl.py index 54878be2e..146284aaa 100644 --- a/src/interdomain/service/InterdomainServiceServicerImpl.py +++ b/src/interdomain/service/InterdomainServiceServicerImpl.py @@ -34,7 +34,7 @@ from common.tools.object_factory.Device import json_device_id from common.tools.object_factory.EndPoint import json_endpoint_id from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient -from dlt.connector.client.DltConnectorClient import DltConnectorClient +from src.dlt.connector.client.DltConnectorClientAsync import DltConnectorClientAsync from pathcomp.frontend.client.PathCompClient import PathCompClient from service.client.ServiceClient import ServiceClient from slice.client.SliceClient import SliceClient @@ -88,7 +88,7 @@ class InterdomainServiceServicerImpl(InterdomainServiceServicer): ]) if len(env_vars) == 2: # DLT available - dlt_connector_client = DltConnectorClient() + dlt_connector_client = DltConnectorClientAsync() dlt_connector_client.connect() else: dlt_connector_client = None diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index d6d5ef0f6..104f2e378 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# DltRecordSender.py - import logging import asyncio @@ -23,7 +21,7 @@ from common.proto.context_pb2 import Device, Link, Service, Slice, TopologyId from common.proto.dlt_connector_pb2 import DltDeviceId, DltLinkId, DltServiceId, DltSliceId from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient -from dlt.connector.client.DltConnectorClient import DltConnectorClient +from src.dlt.connector.client.DltConnectorClientAsync import DltConnectorClientAsync LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) @@ -32,7 +30,7 @@ class DltRecordSender: def __init__(self, context_client: ContextClient) -> None: self.context_client = context_client LOGGER.debug('Creating Servicer...') - self.dlt_connector_client = DltConnectorClient() + self.dlt_connector_client = DltConnectorClientAsync() LOGGER.debug('Servicer Created') self.dlt_record_uuids: List[str] = list() self.dlt_record_uuid_to_data: Dict[str, Tuple[TopologyId, object]] = dict() diff --git a/src/interdomain/service/topology_abstractor/TopologyAbstractor.py b/src/interdomain/service/topology_abstractor/TopologyAbstractor.py index 2a6ef5e32..0dbb332db 100644 --- a/src/interdomain/service/topology_abstractor/TopologyAbstractor.py +++ b/src/interdomain/service/topology_abstractor/TopologyAbstractor.py @@ -33,7 +33,7 @@ from common.tools.object_factory.Device import json_device_id from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient from context.client.EventsCollector import EventsCollector -from dlt.connector.client.DltConnectorClient import DltConnectorClient +from src.dlt.connector.client.DltConnectorClient import DltConnectorClient from .AbstractDevice import AbstractDevice from .AbstractLink import AbstractLink from .DltRecordSender import DltRecordSender diff --git a/src/load_generator/load_gen/DltTools.py b/src/load_generator/load_gen/DltTools.py index 0ac7ab3f0..8321dffa5 100644 --- a/src/load_generator/load_gen/DltTools.py +++ b/src/load_generator/load_gen/DltTools.py @@ -19,7 +19,7 @@ from common.proto.dlt_connector_pb2 import DltDeviceId, DltLinkId, DltServiceId, from common.tools.grpc.Tools import grpc_message_to_json_string from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient -from dlt.connector.client.DltConnectorClient import DltConnectorClient +from src.dlt.connector.client.DltConnectorClient import DltConnectorClient def explore_entities_to_record( slice_id : Optional[SliceId] = None, service_id : Optional[ServiceId] = None diff --git a/src/load_generator/load_gen/RequestGenerator.py b/src/load_generator/load_gen/RequestGenerator.py index 2a3e89fe0..e6e934b68 100644 --- a/src/load_generator/load_gen/RequestGenerator.py +++ b/src/load_generator/load_gen/RequestGenerator.py @@ -27,7 +27,7 @@ from common.tools.object_factory.Service import ( from common.tools.object_factory.Slice import json_slice from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient -from dlt.connector.client.DltConnectorClient import DltConnectorClient +from src.dlt.connector.client.DltConnectorClient import DltConnectorClient from load_generator.tools.ListScalarRange import generate_value from .Constants import ENDPOINT_COMPATIBILITY, RequestType from .DltTools import record_device_to_dlt, record_link_to_dlt diff --git a/src/load_generator/tests/test_dlt_functional.py b/src/load_generator/tests/test_dlt_functional.py index f3dea3b4a..588200e0c 100644 --- a/src/load_generator/tests/test_dlt_functional.py +++ b/src/load_generator/tests/test_dlt_functional.py @@ -19,7 +19,7 @@ from common.proto.dlt_connector_pb2 import DltDeviceId, DltLinkId, DltServiceId, from common.tools.object_factory.Device import json_device from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient -from dlt.connector.client.DltConnectorClient import DltConnectorClient +from src.dlt.connector.client.DltConnectorClient import DltConnectorClient def record_device_to_dlt( dlt_connector_client : DltConnectorClient, domain_id : TopologyId, device_id : DeviceId, delete : bool = False -- GitLab From ff0877b92b7cf4e5003f8b9c9f6a67b0c0b49859 Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Mon, 9 Sep 2024 16:27:04 +0200 Subject: [PATCH 75/94] Code cleanup --- src/dlt/gateway/Dockerfile | 3 +- src/dlt/gateway/dltApp/proto/context.proto | 48 - .../gateway/dltApp/proto/dlt_service.proto | 97 - .../dltApp/proto/dlt_service_grpc_pb.js | 181 -- .../gateway/dltApp/proto/dlt_service_pb.js | 1633 ----------------- src/dlt/gateway/dltApp/src/dltGateway.js | 2 +- src/dlt/gateway/dltApp/tests/testEvents.js | 2 +- src/dlt/gateway/dltApp/tests/testGateway.js | 2 +- 8 files changed, 5 insertions(+), 1963 deletions(-) delete mode 100644 src/dlt/gateway/dltApp/proto/context.proto delete mode 100644 src/dlt/gateway/dltApp/proto/dlt_service.proto delete mode 100644 src/dlt/gateway/dltApp/proto/dlt_service_grpc_pb.js delete mode 100644 src/dlt/gateway/dltApp/proto/dlt_service_pb.js diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile index 694cc521f..66b7e0b30 100644 --- a/src/dlt/gateway/Dockerfile +++ b/src/dlt/gateway/Dockerfile @@ -10,7 +10,8 @@ COPY src/dlt/gateway/dltApp/package*.json ./ # Copy tsconfig.json COPY src/dlt/gateway/dltApp/tsconfig*.json ./ # Copy the proto folder -COPY src/dlt/gateway/dltApp/proto/ ./proto +COPY proto/context.proto ./proto +COPY proto/dlt_gateway.proto ./proto # Copy the src folder COPY src/dlt/gateway/dltApp/src/ ./src diff --git a/src/dlt/gateway/dltApp/proto/context.proto b/src/dlt/gateway/dltApp/proto/context.proto deleted file mode 100644 index dccb72dfa..000000000 --- a/src/dlt/gateway/dltApp/proto/context.proto +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; -package context; - -message Empty {} - -message Uuid { - string uuid = 1; -} - -enum EventTypeEnum { - EVENTTYPE_UNDEFINED = 0; - EVENTTYPE_CREATE = 1; - EVENTTYPE_UPDATE = 2; - EVENTTYPE_REMOVE = 3; -} - -message Timestamp { - double timestamp = 1; -} - -message Event { - Timestamp timestamp = 1; - EventTypeEnum event_type = 2; -} - -message TeraFlowController { - ContextId context_id = 1; - string ip_address = 2; - uint32 port = 3; -} - -message ContextId { - Uuid context_uuid = 1; -} diff --git a/src/dlt/gateway/dltApp/proto/dlt_service.proto b/src/dlt/gateway/dltApp/proto/dlt_service.proto deleted file mode 100644 index 9f6da08f5..000000000 --- a/src/dlt/gateway/dltApp/proto/dlt_service.proto +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; -package dlt; - -import "context.proto"; - -service DltGatewayService { - rpc RecordToDlt (DltRecord ) returns ( DltRecordStatus ) {} - rpc GetFromDlt (DltRecordId ) returns ( DltRecord ) {} - rpc SubscribeToDlt(DltRecordSubscription ) returns (stream DltRecordEvent ) {} - rpc GetDltStatus (context.TeraFlowController) returns ( DltPeerStatus ) {} // NEC is checking if it is possible - rpc GetDltPeers (context.Empty ) returns ( DltPeerStatusList) {} // NEC is checking if it is possible -} - -enum DltRecordTypeEnum { - DLTRECORDTYPE_UNDEFINED = 0; - DLTRECORDTYPE_CONTEXT = 1; - DLTRECORDTYPE_TOPOLOGY = 2; - DLTRECORDTYPE_DEVICE = 3; - DLTRECORDTYPE_LINK = 4; - DLTRECORDTYPE_SERVICE = 5; - DLTRECORDTYPE_SLICE = 6; -} - -enum DltRecordOperationEnum { - DLTRECORDOPERATION_UNDEFINED = 0; - DLTRECORDOPERATION_ADD = 1; - DLTRECORDOPERATION_UPDATE = 2; - DLTRECORDOPERATION_DELETE = 3; -} - -enum DltRecordStatusEnum { - DLTRECORDSTATUS_UNDEFINED = 0; - DLTRECORDSTATUS_SUCCEEDED = 1; - DLTRECORDSTATUS_FAILED = 2; -} - -enum DltStatusEnum { - DLTSTATUS_UNDEFINED = 0; - DLTSTATUS_NOTAVAILABLE = 1; - DLTSTATUS_INITIALIZED = 2; - DLTSTATUS_AVAILABLE = 3; - DLTSTATUS_DEINIT = 4; -} - -message DltRecordId { - context.Uuid domain_uuid = 1; // unique identifier of domain owning the record - DltRecordTypeEnum type = 2; // type of record - context.Uuid record_uuid = 3; // unique identifier of the record within the domain context_uuid/topology_uuid -} - -message DltRecord { - DltRecordId record_id = 1; // record identifier - DltRecordOperationEnum operation = 2; // operation to be performed over the record - string data_json = 3; // record content: JSON-encoded record content -} - -message DltRecordSubscription { - // retrieved events have to match ALL conditions. - // i.e., type in types requested, AND operation in operations requested - // TODO: consider adding a more sophisticated filtering - repeated DltRecordTypeEnum type = 1; // selected event types, empty=all - repeated DltRecordOperationEnum operation = 2; // selected event operations, empty=all -} - -message DltRecordEvent { - context.Event event = 1; // common event data (timestamp & event_type) - DltRecordId record_id = 2; // record identifier associated with this event -} - -message DltRecordStatus { - DltRecordId record_id = 1; // identifier of the associated record - DltRecordStatusEnum status = 2; // status of the record - string error_message = 3; // error message in case of failure, empty otherwise -} - -message DltPeerStatus { - context.TeraFlowController controller = 1; // Identifier of the TeraFlow controller instance - DltStatusEnum status = 2; // Status of the TeraFlow controller instance -} - -message DltPeerStatusList { - repeated DltPeerStatus peers = 1; // List of peers and their status -} diff --git a/src/dlt/gateway/dltApp/proto/dlt_service_grpc_pb.js b/src/dlt/gateway/dltApp/proto/dlt_service_grpc_pb.js deleted file mode 100644 index 85af635c7..000000000 --- a/src/dlt/gateway/dltApp/proto/dlt_service_grpc_pb.js +++ /dev/null @@ -1,181 +0,0 @@ -// GENERATED CODE -- DO NOT EDIT! - -// Original file comments: -// Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -'use strict'; -var grpc = require('grpc'); -var dlt_service_pb = require('./dlt_service_pb.js'); -var context_pb = require('./context_pb.js'); - -function serialize_context_Empty(arg) { - if (!(arg instanceof context_pb.Empty)) { - throw new Error('Expected argument of type context.Empty'); - } - return Buffer.from(arg.serializeBinary()); -} - -function deserialize_context_Empty(buffer_arg) { - return context_pb.Empty.deserializeBinary(new Uint8Array(buffer_arg)); -} - -function serialize_context_TeraFlowController(arg) { - if (!(arg instanceof context_pb.TeraFlowController)) { - throw new Error('Expected argument of type context.TeraFlowController'); - } - return Buffer.from(arg.serializeBinary()); -} - -function deserialize_context_TeraFlowController(buffer_arg) { - return context_pb.TeraFlowController.deserializeBinary(new Uint8Array(buffer_arg)); -} - -function serialize_dlt_DltPeerStatus(arg) { - if (!(arg instanceof dlt_service_pb.DltPeerStatus)) { - throw new Error('Expected argument of type dlt.DltPeerStatus'); - } - return Buffer.from(arg.serializeBinary()); -} - -function deserialize_dlt_DltPeerStatus(buffer_arg) { - return dlt_service_pb.DltPeerStatus.deserializeBinary(new Uint8Array(buffer_arg)); -} - -function serialize_dlt_DltPeerStatusList(arg) { - if (!(arg instanceof dlt_service_pb.DltPeerStatusList)) { - throw new Error('Expected argument of type dlt.DltPeerStatusList'); - } - return Buffer.from(arg.serializeBinary()); -} - -function deserialize_dlt_DltPeerStatusList(buffer_arg) { - return dlt_service_pb.DltPeerStatusList.deserializeBinary(new Uint8Array(buffer_arg)); -} - -function serialize_dlt_DltRecord(arg) { - if (!(arg instanceof dlt_service_pb.DltRecord)) { - throw new Error('Expected argument of type dlt.DltRecord'); - } - return Buffer.from(arg.serializeBinary()); -} - -function deserialize_dlt_DltRecord(buffer_arg) { - return dlt_service_pb.DltRecord.deserializeBinary(new Uint8Array(buffer_arg)); -} - -function serialize_dlt_DltRecordEvent(arg) { - if (!(arg instanceof dlt_service_pb.DltRecordEvent)) { - throw new Error('Expected argument of type dlt.DltRecordEvent'); - } - return Buffer.from(arg.serializeBinary()); -} - -function deserialize_dlt_DltRecordEvent(buffer_arg) { - return dlt_service_pb.DltRecordEvent.deserializeBinary(new Uint8Array(buffer_arg)); -} - -function serialize_dlt_DltRecordId(arg) { - if (!(arg instanceof dlt_service_pb.DltRecordId)) { - throw new Error('Expected argument of type dlt.DltRecordId'); - } - return Buffer.from(arg.serializeBinary()); -} - -function deserialize_dlt_DltRecordId(buffer_arg) { - return dlt_service_pb.DltRecordId.deserializeBinary(new Uint8Array(buffer_arg)); -} - -function serialize_dlt_DltRecordStatus(arg) { - if (!(arg instanceof dlt_service_pb.DltRecordStatus)) { - throw new Error('Expected argument of type dlt.DltRecordStatus'); - } - return Buffer.from(arg.serializeBinary()); -} - -function deserialize_dlt_DltRecordStatus(buffer_arg) { - return dlt_service_pb.DltRecordStatus.deserializeBinary(new Uint8Array(buffer_arg)); -} - -function serialize_dlt_DltRecordSubscription(arg) { - if (!(arg instanceof dlt_service_pb.DltRecordSubscription)) { - throw new Error('Expected argument of type dlt.DltRecordSubscription'); - } - return Buffer.from(arg.serializeBinary()); -} - -function deserialize_dlt_DltRecordSubscription(buffer_arg) { - return dlt_service_pb.DltRecordSubscription.deserializeBinary(new Uint8Array(buffer_arg)); -} - - -var DltGatewayServiceService = exports.DltGatewayServiceService = { - recordToDlt: { - path: '/dlt.DltGatewayService/RecordToDlt', - requestStream: false, - responseStream: false, - requestType: dlt_service_pb.DltRecord, - responseType: dlt_service_pb.DltRecordStatus, - requestSerialize: serialize_dlt_DltRecord, - requestDeserialize: deserialize_dlt_DltRecord, - responseSerialize: serialize_dlt_DltRecordStatus, - responseDeserialize: deserialize_dlt_DltRecordStatus, - }, - getFromDlt: { - path: '/dlt.DltGatewayService/GetFromDlt', - requestStream: false, - responseStream: false, - requestType: dlt_service_pb.DltRecordId, - responseType: dlt_service_pb.DltRecord, - requestSerialize: serialize_dlt_DltRecordId, - requestDeserialize: deserialize_dlt_DltRecordId, - responseSerialize: serialize_dlt_DltRecord, - responseDeserialize: deserialize_dlt_DltRecord, - }, - subscribeToDlt: { - path: '/dlt.DltGatewayService/SubscribeToDlt', - requestStream: false, - responseStream: true, - requestType: dlt_service_pb.DltRecordSubscription, - responseType: dlt_service_pb.DltRecordEvent, - requestSerialize: serialize_dlt_DltRecordSubscription, - requestDeserialize: deserialize_dlt_DltRecordSubscription, - responseSerialize: serialize_dlt_DltRecordEvent, - responseDeserialize: deserialize_dlt_DltRecordEvent, - }, - getDltStatus: { - path: '/dlt.DltGatewayService/GetDltStatus', - requestStream: false, - responseStream: false, - requestType: context_pb.TeraFlowController, - responseType: dlt_service_pb.DltPeerStatus, - requestSerialize: serialize_context_TeraFlowController, - requestDeserialize: deserialize_context_TeraFlowController, - responseSerialize: serialize_dlt_DltPeerStatus, - responseDeserialize: deserialize_dlt_DltPeerStatus, - }, - getDltPeers: { - path: '/dlt.DltGatewayService/GetDltPeers', - requestStream: false, - responseStream: false, - requestType: context_pb.Empty, - responseType: dlt_service_pb.DltPeerStatusList, - requestSerialize: serialize_context_Empty, - requestDeserialize: deserialize_context_Empty, - responseSerialize: serialize_dlt_DltPeerStatusList, - responseDeserialize: deserialize_dlt_DltPeerStatusList, - }, -}; - -exports.DltGatewayServiceClient = grpc.makeGenericClientConstructor(DltGatewayServiceService); diff --git a/src/dlt/gateway/dltApp/proto/dlt_service_pb.js b/src/dlt/gateway/dltApp/proto/dlt_service_pb.js deleted file mode 100644 index 59782ff7a..000000000 --- a/src/dlt/gateway/dltApp/proto/dlt_service_pb.js +++ /dev/null @@ -1,1633 +0,0 @@ -// source: dlt_service.proto -/** - * @fileoverview - * @enhanceable - * @suppress {missingRequire} reports error on implicit type usages. - * @suppress {messageConventions} JS Compiler reports an error if a variable or - * field starts with 'MSG_' and isn't a translatable message. - * @public - */ -// GENERATED CODE -- DO NOT EDIT! -/* eslint-disable */ -// @ts-nocheck - -var jspb = require('google-protobuf'); -var goog = jspb; -var global = (function() { - if (this) { return this; } - if (typeof window !== 'undefined') { return window; } - if (typeof global !== 'undefined') { return global; } - if (typeof self !== 'undefined') { return self; } - return Function('return this')(); -}.call(null)); - -var context_pb = require('./context_pb.js'); -goog.object.extend(proto, context_pb); -goog.exportSymbol('proto.dlt.DltPeerStatus', null, global); -goog.exportSymbol('proto.dlt.DltPeerStatusList', null, global); -goog.exportSymbol('proto.dlt.DltRecord', null, global); -goog.exportSymbol('proto.dlt.DltRecordEvent', null, global); -goog.exportSymbol('proto.dlt.DltRecordId', null, global); -goog.exportSymbol('proto.dlt.DltRecordOperationEnum', null, global); -goog.exportSymbol('proto.dlt.DltRecordStatus', null, global); -goog.exportSymbol('proto.dlt.DltRecordStatusEnum', null, global); -goog.exportSymbol('proto.dlt.DltRecordSubscription', null, global); -goog.exportSymbol('proto.dlt.DltRecordTypeEnum', null, global); -goog.exportSymbol('proto.dlt.DltStatusEnum', null, global); -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.dlt.DltRecordId = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); -}; -goog.inherits(proto.dlt.DltRecordId, jspb.Message); -if (goog.DEBUG && !COMPILED) { - /** - * @public - * @override - */ - proto.dlt.DltRecordId.displayName = 'proto.dlt.DltRecordId'; -} -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.dlt.DltRecord = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); -}; -goog.inherits(proto.dlt.DltRecord, jspb.Message); -if (goog.DEBUG && !COMPILED) { - /** - * @public - * @override - */ - proto.dlt.DltRecord.displayName = 'proto.dlt.DltRecord'; -} -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.dlt.DltRecordSubscription = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, proto.dlt.DltRecordSubscription.repeatedFields_, null); -}; -goog.inherits(proto.dlt.DltRecordSubscription, jspb.Message); -if (goog.DEBUG && !COMPILED) { - /** - * @public - * @override - */ - proto.dlt.DltRecordSubscription.displayName = 'proto.dlt.DltRecordSubscription'; -} -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.dlt.DltRecordEvent = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); -}; -goog.inherits(proto.dlt.DltRecordEvent, jspb.Message); -if (goog.DEBUG && !COMPILED) { - /** - * @public - * @override - */ - proto.dlt.DltRecordEvent.displayName = 'proto.dlt.DltRecordEvent'; -} -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.dlt.DltRecordStatus = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); -}; -goog.inherits(proto.dlt.DltRecordStatus, jspb.Message); -if (goog.DEBUG && !COMPILED) { - /** - * @public - * @override - */ - proto.dlt.DltRecordStatus.displayName = 'proto.dlt.DltRecordStatus'; -} -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.dlt.DltPeerStatus = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); -}; -goog.inherits(proto.dlt.DltPeerStatus, jspb.Message); -if (goog.DEBUG && !COMPILED) { - /** - * @public - * @override - */ - proto.dlt.DltPeerStatus.displayName = 'proto.dlt.DltPeerStatus'; -} -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.dlt.DltPeerStatusList = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, proto.dlt.DltPeerStatusList.repeatedFields_, null); -}; -goog.inherits(proto.dlt.DltPeerStatusList, jspb.Message); -if (goog.DEBUG && !COMPILED) { - /** - * @public - * @override - */ - proto.dlt.DltPeerStatusList.displayName = 'proto.dlt.DltPeerStatusList'; -} - - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * Optional fields that are not set will be set to undefined. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * net/proto2/compiler/js/internal/generator.cc#kKeyword. - * @param {boolean=} opt_includeInstance Deprecated. whether to include the - * JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @return {!Object} - */ -proto.dlt.DltRecordId.prototype.toObject = function(opt_includeInstance) { - return proto.dlt.DltRecordId.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Deprecated. Whether to include - * the JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.dlt.DltRecordId} msg The msg instance to transform. - * @return {!Object} - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltRecordId.toObject = function(includeInstance, msg) { - var f, obj = { - domainUuid: (f = msg.getDomainUuid()) && context_pb.Uuid.toObject(includeInstance, f), - type: jspb.Message.getFieldWithDefault(msg, 2, 0), - recordUuid: (f = msg.getRecordUuid()) && context_pb.Uuid.toObject(includeInstance, f) - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.dlt.DltRecordId} - */ -proto.dlt.DltRecordId.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.dlt.DltRecordId; - return proto.dlt.DltRecordId.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.dlt.DltRecordId} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.dlt.DltRecordId} - */ -proto.dlt.DltRecordId.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = new context_pb.Uuid; - reader.readMessage(value,context_pb.Uuid.deserializeBinaryFromReader); - msg.setDomainUuid(value); - break; - case 2: - var value = /** @type {!proto.dlt.DltRecordTypeEnum} */ (reader.readEnum()); - msg.setType(value); - break; - case 3: - var value = new context_pb.Uuid; - reader.readMessage(value,context_pb.Uuid.deserializeBinaryFromReader); - msg.setRecordUuid(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.dlt.DltRecordId.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - proto.dlt.DltRecordId.serializeBinaryToWriter(this, writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the given message to binary data (in protobuf wire - * format), writing to the given BinaryWriter. - * @param {!proto.dlt.DltRecordId} message - * @param {!jspb.BinaryWriter} writer - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltRecordId.serializeBinaryToWriter = function(message, writer) { - var f = undefined; - f = message.getDomainUuid(); - if (f != null) { - writer.writeMessage( - 1, - f, - context_pb.Uuid.serializeBinaryToWriter - ); - } - f = message.getType(); - if (f !== 0.0) { - writer.writeEnum( - 2, - f - ); - } - f = message.getRecordUuid(); - if (f != null) { - writer.writeMessage( - 3, - f, - context_pb.Uuid.serializeBinaryToWriter - ); - } -}; - - -/** - * optional context.Uuid domain_uuid = 1; - * @return {?proto.context.Uuid} - */ -proto.dlt.DltRecordId.prototype.getDomainUuid = function() { - return /** @type{?proto.context.Uuid} */ ( - jspb.Message.getWrapperField(this, context_pb.Uuid, 1)); -}; - - -/** - * @param {?proto.context.Uuid|undefined} value - * @return {!proto.dlt.DltRecordId} returns this -*/ -proto.dlt.DltRecordId.prototype.setDomainUuid = function(value) { - return jspb.Message.setWrapperField(this, 1, value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.dlt.DltRecordId} returns this - */ -proto.dlt.DltRecordId.prototype.clearDomainUuid = function() { - return this.setDomainUuid(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.dlt.DltRecordId.prototype.hasDomainUuid = function() { - return jspb.Message.getField(this, 1) != null; -}; - - -/** - * optional DltRecordTypeEnum type = 2; - * @return {!proto.dlt.DltRecordTypeEnum} - */ -proto.dlt.DltRecordId.prototype.getType = function() { - return /** @type {!proto.dlt.DltRecordTypeEnum} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); -}; - - -/** - * @param {!proto.dlt.DltRecordTypeEnum} value - * @return {!proto.dlt.DltRecordId} returns this - */ -proto.dlt.DltRecordId.prototype.setType = function(value) { - return jspb.Message.setProto3EnumField(this, 2, value); -}; - - -/** - * optional context.Uuid record_uuid = 3; - * @return {?proto.context.Uuid} - */ -proto.dlt.DltRecordId.prototype.getRecordUuid = function() { - return /** @type{?proto.context.Uuid} */ ( - jspb.Message.getWrapperField(this, context_pb.Uuid, 3)); -}; - - -/** - * @param {?proto.context.Uuid|undefined} value - * @return {!proto.dlt.DltRecordId} returns this -*/ -proto.dlt.DltRecordId.prototype.setRecordUuid = function(value) { - return jspb.Message.setWrapperField(this, 3, value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.dlt.DltRecordId} returns this - */ -proto.dlt.DltRecordId.prototype.clearRecordUuid = function() { - return this.setRecordUuid(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.dlt.DltRecordId.prototype.hasRecordUuid = function() { - return jspb.Message.getField(this, 3) != null; -}; - - - - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * Optional fields that are not set will be set to undefined. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * net/proto2/compiler/js/internal/generator.cc#kKeyword. - * @param {boolean=} opt_includeInstance Deprecated. whether to include the - * JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @return {!Object} - */ -proto.dlt.DltRecord.prototype.toObject = function(opt_includeInstance) { - return proto.dlt.DltRecord.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Deprecated. Whether to include - * the JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.dlt.DltRecord} msg The msg instance to transform. - * @return {!Object} - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltRecord.toObject = function(includeInstance, msg) { - var f, obj = { - recordId: (f = msg.getRecordId()) && proto.dlt.DltRecordId.toObject(includeInstance, f), - operation: jspb.Message.getFieldWithDefault(msg, 2, 0), - dataJson: jspb.Message.getFieldWithDefault(msg, 3, "") - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.dlt.DltRecord} - */ -proto.dlt.DltRecord.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.dlt.DltRecord; - return proto.dlt.DltRecord.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.dlt.DltRecord} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.dlt.DltRecord} - */ -proto.dlt.DltRecord.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = new proto.dlt.DltRecordId; - reader.readMessage(value,proto.dlt.DltRecordId.deserializeBinaryFromReader); - msg.setRecordId(value); - break; - case 2: - var value = /** @type {!proto.dlt.DltRecordOperationEnum} */ (reader.readEnum()); - msg.setOperation(value); - break; - case 3: - var value = /** @type {string} */ (reader.readString()); - msg.setDataJson(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.dlt.DltRecord.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - proto.dlt.DltRecord.serializeBinaryToWriter(this, writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the given message to binary data (in protobuf wire - * format), writing to the given BinaryWriter. - * @param {!proto.dlt.DltRecord} message - * @param {!jspb.BinaryWriter} writer - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltRecord.serializeBinaryToWriter = function(message, writer) { - var f = undefined; - f = message.getRecordId(); - if (f != null) { - writer.writeMessage( - 1, - f, - proto.dlt.DltRecordId.serializeBinaryToWriter - ); - } - f = message.getOperation(); - if (f !== 0.0) { - writer.writeEnum( - 2, - f - ); - } - f = message.getDataJson(); - if (f.length > 0) { - writer.writeString( - 3, - f - ); - } -}; - - -/** - * optional DltRecordId record_id = 1; - * @return {?proto.dlt.DltRecordId} - */ -proto.dlt.DltRecord.prototype.getRecordId = function() { - return /** @type{?proto.dlt.DltRecordId} */ ( - jspb.Message.getWrapperField(this, proto.dlt.DltRecordId, 1)); -}; - - -/** - * @param {?proto.dlt.DltRecordId|undefined} value - * @return {!proto.dlt.DltRecord} returns this -*/ -proto.dlt.DltRecord.prototype.setRecordId = function(value) { - return jspb.Message.setWrapperField(this, 1, value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.dlt.DltRecord} returns this - */ -proto.dlt.DltRecord.prototype.clearRecordId = function() { - return this.setRecordId(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.dlt.DltRecord.prototype.hasRecordId = function() { - return jspb.Message.getField(this, 1) != null; -}; - - -/** - * optional DltRecordOperationEnum operation = 2; - * @return {!proto.dlt.DltRecordOperationEnum} - */ -proto.dlt.DltRecord.prototype.getOperation = function() { - return /** @type {!proto.dlt.DltRecordOperationEnum} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); -}; - - -/** - * @param {!proto.dlt.DltRecordOperationEnum} value - * @return {!proto.dlt.DltRecord} returns this - */ -proto.dlt.DltRecord.prototype.setOperation = function(value) { - return jspb.Message.setProto3EnumField(this, 2, value); -}; - - -/** - * optional string data_json = 3; - * @return {string} - */ -proto.dlt.DltRecord.prototype.getDataJson = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); -}; - - -/** - * @param {string} value - * @return {!proto.dlt.DltRecord} returns this - */ -proto.dlt.DltRecord.prototype.setDataJson = function(value) { - return jspb.Message.setProto3StringField(this, 3, value); -}; - - - -/** - * List of repeated fields within this message type. - * @private {!Array} - * @const - */ -proto.dlt.DltRecordSubscription.repeatedFields_ = [1,2]; - - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * Optional fields that are not set will be set to undefined. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * net/proto2/compiler/js/internal/generator.cc#kKeyword. - * @param {boolean=} opt_includeInstance Deprecated. whether to include the - * JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @return {!Object} - */ -proto.dlt.DltRecordSubscription.prototype.toObject = function(opt_includeInstance) { - return proto.dlt.DltRecordSubscription.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Deprecated. Whether to include - * the JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.dlt.DltRecordSubscription} msg The msg instance to transform. - * @return {!Object} - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltRecordSubscription.toObject = function(includeInstance, msg) { - var f, obj = { - typeList: (f = jspb.Message.getRepeatedField(msg, 1)) == null ? undefined : f, - operationList: (f = jspb.Message.getRepeatedField(msg, 2)) == null ? undefined : f - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.dlt.DltRecordSubscription} - */ -proto.dlt.DltRecordSubscription.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.dlt.DltRecordSubscription; - return proto.dlt.DltRecordSubscription.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.dlt.DltRecordSubscription} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.dlt.DltRecordSubscription} - */ -proto.dlt.DltRecordSubscription.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var values = /** @type {!Array} */ (reader.isDelimited() ? reader.readPackedEnum() : [reader.readEnum()]); - for (var i = 0; i < values.length; i++) { - msg.addType(values[i]); - } - break; - case 2: - var values = /** @type {!Array} */ (reader.isDelimited() ? reader.readPackedEnum() : [reader.readEnum()]); - for (var i = 0; i < values.length; i++) { - msg.addOperation(values[i]); - } - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.dlt.DltRecordSubscription.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - proto.dlt.DltRecordSubscription.serializeBinaryToWriter(this, writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the given message to binary data (in protobuf wire - * format), writing to the given BinaryWriter. - * @param {!proto.dlt.DltRecordSubscription} message - * @param {!jspb.BinaryWriter} writer - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltRecordSubscription.serializeBinaryToWriter = function(message, writer) { - var f = undefined; - f = message.getTypeList(); - if (f.length > 0) { - writer.writePackedEnum( - 1, - f - ); - } - f = message.getOperationList(); - if (f.length > 0) { - writer.writePackedEnum( - 2, - f - ); - } -}; - - -/** - * repeated DltRecordTypeEnum type = 1; - * @return {!Array} - */ -proto.dlt.DltRecordSubscription.prototype.getTypeList = function() { - return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 1)); -}; - - -/** - * @param {!Array} value - * @return {!proto.dlt.DltRecordSubscription} returns this - */ -proto.dlt.DltRecordSubscription.prototype.setTypeList = function(value) { - return jspb.Message.setField(this, 1, value || []); -}; - - -/** - * @param {!proto.dlt.DltRecordTypeEnum} value - * @param {number=} opt_index - * @return {!proto.dlt.DltRecordSubscription} returns this - */ -proto.dlt.DltRecordSubscription.prototype.addType = function(value, opt_index) { - return jspb.Message.addToRepeatedField(this, 1, value, opt_index); -}; - - -/** - * Clears the list making it empty but non-null. - * @return {!proto.dlt.DltRecordSubscription} returns this - */ -proto.dlt.DltRecordSubscription.prototype.clearTypeList = function() { - return this.setTypeList([]); -}; - - -/** - * repeated DltRecordOperationEnum operation = 2; - * @return {!Array} - */ -proto.dlt.DltRecordSubscription.prototype.getOperationList = function() { - return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 2)); -}; - - -/** - * @param {!Array} value - * @return {!proto.dlt.DltRecordSubscription} returns this - */ -proto.dlt.DltRecordSubscription.prototype.setOperationList = function(value) { - return jspb.Message.setField(this, 2, value || []); -}; - - -/** - * @param {!proto.dlt.DltRecordOperationEnum} value - * @param {number=} opt_index - * @return {!proto.dlt.DltRecordSubscription} returns this - */ -proto.dlt.DltRecordSubscription.prototype.addOperation = function(value, opt_index) { - return jspb.Message.addToRepeatedField(this, 2, value, opt_index); -}; - - -/** - * Clears the list making it empty but non-null. - * @return {!proto.dlt.DltRecordSubscription} returns this - */ -proto.dlt.DltRecordSubscription.prototype.clearOperationList = function() { - return this.setOperationList([]); -}; - - - - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * Optional fields that are not set will be set to undefined. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * net/proto2/compiler/js/internal/generator.cc#kKeyword. - * @param {boolean=} opt_includeInstance Deprecated. whether to include the - * JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @return {!Object} - */ -proto.dlt.DltRecordEvent.prototype.toObject = function(opt_includeInstance) { - return proto.dlt.DltRecordEvent.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Deprecated. Whether to include - * the JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.dlt.DltRecordEvent} msg The msg instance to transform. - * @return {!Object} - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltRecordEvent.toObject = function(includeInstance, msg) { - var f, obj = { - event: (f = msg.getEvent()) && context_pb.Event.toObject(includeInstance, f), - recordId: (f = msg.getRecordId()) && proto.dlt.DltRecordId.toObject(includeInstance, f) - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.dlt.DltRecordEvent} - */ -proto.dlt.DltRecordEvent.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.dlt.DltRecordEvent; - return proto.dlt.DltRecordEvent.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.dlt.DltRecordEvent} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.dlt.DltRecordEvent} - */ -proto.dlt.DltRecordEvent.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = new context_pb.Event; - reader.readMessage(value,context_pb.Event.deserializeBinaryFromReader); - msg.setEvent(value); - break; - case 2: - var value = new proto.dlt.DltRecordId; - reader.readMessage(value,proto.dlt.DltRecordId.deserializeBinaryFromReader); - msg.setRecordId(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.dlt.DltRecordEvent.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - proto.dlt.DltRecordEvent.serializeBinaryToWriter(this, writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the given message to binary data (in protobuf wire - * format), writing to the given BinaryWriter. - * @param {!proto.dlt.DltRecordEvent} message - * @param {!jspb.BinaryWriter} writer - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltRecordEvent.serializeBinaryToWriter = function(message, writer) { - var f = undefined; - f = message.getEvent(); - if (f != null) { - writer.writeMessage( - 1, - f, - context_pb.Event.serializeBinaryToWriter - ); - } - f = message.getRecordId(); - if (f != null) { - writer.writeMessage( - 2, - f, - proto.dlt.DltRecordId.serializeBinaryToWriter - ); - } -}; - - -/** - * optional context.Event event = 1; - * @return {?proto.context.Event} - */ -proto.dlt.DltRecordEvent.prototype.getEvent = function() { - return /** @type{?proto.context.Event} */ ( - jspb.Message.getWrapperField(this, context_pb.Event, 1)); -}; - - -/** - * @param {?proto.context.Event|undefined} value - * @return {!proto.dlt.DltRecordEvent} returns this -*/ -proto.dlt.DltRecordEvent.prototype.setEvent = function(value) { - return jspb.Message.setWrapperField(this, 1, value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.dlt.DltRecordEvent} returns this - */ -proto.dlt.DltRecordEvent.prototype.clearEvent = function() { - return this.setEvent(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.dlt.DltRecordEvent.prototype.hasEvent = function() { - return jspb.Message.getField(this, 1) != null; -}; - - -/** - * optional DltRecordId record_id = 2; - * @return {?proto.dlt.DltRecordId} - */ -proto.dlt.DltRecordEvent.prototype.getRecordId = function() { - return /** @type{?proto.dlt.DltRecordId} */ ( - jspb.Message.getWrapperField(this, proto.dlt.DltRecordId, 2)); -}; - - -/** - * @param {?proto.dlt.DltRecordId|undefined} value - * @return {!proto.dlt.DltRecordEvent} returns this -*/ -proto.dlt.DltRecordEvent.prototype.setRecordId = function(value) { - return jspb.Message.setWrapperField(this, 2, value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.dlt.DltRecordEvent} returns this - */ -proto.dlt.DltRecordEvent.prototype.clearRecordId = function() { - return this.setRecordId(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.dlt.DltRecordEvent.prototype.hasRecordId = function() { - return jspb.Message.getField(this, 2) != null; -}; - - - - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * Optional fields that are not set will be set to undefined. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * net/proto2/compiler/js/internal/generator.cc#kKeyword. - * @param {boolean=} opt_includeInstance Deprecated. whether to include the - * JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @return {!Object} - */ -proto.dlt.DltRecordStatus.prototype.toObject = function(opt_includeInstance) { - return proto.dlt.DltRecordStatus.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Deprecated. Whether to include - * the JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.dlt.DltRecordStatus} msg The msg instance to transform. - * @return {!Object} - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltRecordStatus.toObject = function(includeInstance, msg) { - var f, obj = { - recordId: (f = msg.getRecordId()) && proto.dlt.DltRecordId.toObject(includeInstance, f), - status: jspb.Message.getFieldWithDefault(msg, 2, 0), - errorMessage: jspb.Message.getFieldWithDefault(msg, 3, "") - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.dlt.DltRecordStatus} - */ -proto.dlt.DltRecordStatus.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.dlt.DltRecordStatus; - return proto.dlt.DltRecordStatus.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.dlt.DltRecordStatus} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.dlt.DltRecordStatus} - */ -proto.dlt.DltRecordStatus.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = new proto.dlt.DltRecordId; - reader.readMessage(value,proto.dlt.DltRecordId.deserializeBinaryFromReader); - msg.setRecordId(value); - break; - case 2: - var value = /** @type {!proto.dlt.DltRecordStatusEnum} */ (reader.readEnum()); - msg.setStatus(value); - break; - case 3: - var value = /** @type {string} */ (reader.readString()); - msg.setErrorMessage(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.dlt.DltRecordStatus.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - proto.dlt.DltRecordStatus.serializeBinaryToWriter(this, writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the given message to binary data (in protobuf wire - * format), writing to the given BinaryWriter. - * @param {!proto.dlt.DltRecordStatus} message - * @param {!jspb.BinaryWriter} writer - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltRecordStatus.serializeBinaryToWriter = function(message, writer) { - var f = undefined; - f = message.getRecordId(); - if (f != null) { - writer.writeMessage( - 1, - f, - proto.dlt.DltRecordId.serializeBinaryToWriter - ); - } - f = message.getStatus(); - if (f !== 0.0) { - writer.writeEnum( - 2, - f - ); - } - f = message.getErrorMessage(); - if (f.length > 0) { - writer.writeString( - 3, - f - ); - } -}; - - -/** - * optional DltRecordId record_id = 1; - * @return {?proto.dlt.DltRecordId} - */ -proto.dlt.DltRecordStatus.prototype.getRecordId = function() { - return /** @type{?proto.dlt.DltRecordId} */ ( - jspb.Message.getWrapperField(this, proto.dlt.DltRecordId, 1)); -}; - - -/** - * @param {?proto.dlt.DltRecordId|undefined} value - * @return {!proto.dlt.DltRecordStatus} returns this -*/ -proto.dlt.DltRecordStatus.prototype.setRecordId = function(value) { - return jspb.Message.setWrapperField(this, 1, value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.dlt.DltRecordStatus} returns this - */ -proto.dlt.DltRecordStatus.prototype.clearRecordId = function() { - return this.setRecordId(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.dlt.DltRecordStatus.prototype.hasRecordId = function() { - return jspb.Message.getField(this, 1) != null; -}; - - -/** - * optional DltRecordStatusEnum status = 2; - * @return {!proto.dlt.DltRecordStatusEnum} - */ -proto.dlt.DltRecordStatus.prototype.getStatus = function() { - return /** @type {!proto.dlt.DltRecordStatusEnum} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); -}; - - -/** - * @param {!proto.dlt.DltRecordStatusEnum} value - * @return {!proto.dlt.DltRecordStatus} returns this - */ -proto.dlt.DltRecordStatus.prototype.setStatus = function(value) { - return jspb.Message.setProto3EnumField(this, 2, value); -}; - - -/** - * optional string error_message = 3; - * @return {string} - */ -proto.dlt.DltRecordStatus.prototype.getErrorMessage = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); -}; - - -/** - * @param {string} value - * @return {!proto.dlt.DltRecordStatus} returns this - */ -proto.dlt.DltRecordStatus.prototype.setErrorMessage = function(value) { - return jspb.Message.setProto3StringField(this, 3, value); -}; - - - - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * Optional fields that are not set will be set to undefined. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * net/proto2/compiler/js/internal/generator.cc#kKeyword. - * @param {boolean=} opt_includeInstance Deprecated. whether to include the - * JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @return {!Object} - */ -proto.dlt.DltPeerStatus.prototype.toObject = function(opt_includeInstance) { - return proto.dlt.DltPeerStatus.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Deprecated. Whether to include - * the JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.dlt.DltPeerStatus} msg The msg instance to transform. - * @return {!Object} - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltPeerStatus.toObject = function(includeInstance, msg) { - var f, obj = { - controller: (f = msg.getController()) && context_pb.TeraFlowController.toObject(includeInstance, f), - status: jspb.Message.getFieldWithDefault(msg, 2, 0) - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.dlt.DltPeerStatus} - */ -proto.dlt.DltPeerStatus.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.dlt.DltPeerStatus; - return proto.dlt.DltPeerStatus.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.dlt.DltPeerStatus} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.dlt.DltPeerStatus} - */ -proto.dlt.DltPeerStatus.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = new context_pb.TeraFlowController; - reader.readMessage(value,context_pb.TeraFlowController.deserializeBinaryFromReader); - msg.setController(value); - break; - case 2: - var value = /** @type {!proto.dlt.DltStatusEnum} */ (reader.readEnum()); - msg.setStatus(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.dlt.DltPeerStatus.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - proto.dlt.DltPeerStatus.serializeBinaryToWriter(this, writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the given message to binary data (in protobuf wire - * format), writing to the given BinaryWriter. - * @param {!proto.dlt.DltPeerStatus} message - * @param {!jspb.BinaryWriter} writer - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltPeerStatus.serializeBinaryToWriter = function(message, writer) { - var f = undefined; - f = message.getController(); - if (f != null) { - writer.writeMessage( - 1, - f, - context_pb.TeraFlowController.serializeBinaryToWriter - ); - } - f = message.getStatus(); - if (f !== 0.0) { - writer.writeEnum( - 2, - f - ); - } -}; - - -/** - * optional context.TeraFlowController controller = 1; - * @return {?proto.context.TeraFlowController} - */ -proto.dlt.DltPeerStatus.prototype.getController = function() { - return /** @type{?proto.context.TeraFlowController} */ ( - jspb.Message.getWrapperField(this, context_pb.TeraFlowController, 1)); -}; - - -/** - * @param {?proto.context.TeraFlowController|undefined} value - * @return {!proto.dlt.DltPeerStatus} returns this -*/ -proto.dlt.DltPeerStatus.prototype.setController = function(value) { - return jspb.Message.setWrapperField(this, 1, value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.dlt.DltPeerStatus} returns this - */ -proto.dlt.DltPeerStatus.prototype.clearController = function() { - return this.setController(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.dlt.DltPeerStatus.prototype.hasController = function() { - return jspb.Message.getField(this, 1) != null; -}; - - -/** - * optional DltStatusEnum status = 2; - * @return {!proto.dlt.DltStatusEnum} - */ -proto.dlt.DltPeerStatus.prototype.getStatus = function() { - return /** @type {!proto.dlt.DltStatusEnum} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); -}; - - -/** - * @param {!proto.dlt.DltStatusEnum} value - * @return {!proto.dlt.DltPeerStatus} returns this - */ -proto.dlt.DltPeerStatus.prototype.setStatus = function(value) { - return jspb.Message.setProto3EnumField(this, 2, value); -}; - - - -/** - * List of repeated fields within this message type. - * @private {!Array} - * @const - */ -proto.dlt.DltPeerStatusList.repeatedFields_ = [1]; - - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * Optional fields that are not set will be set to undefined. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * net/proto2/compiler/js/internal/generator.cc#kKeyword. - * @param {boolean=} opt_includeInstance Deprecated. whether to include the - * JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @return {!Object} - */ -proto.dlt.DltPeerStatusList.prototype.toObject = function(opt_includeInstance) { - return proto.dlt.DltPeerStatusList.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Deprecated. Whether to include - * the JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.dlt.DltPeerStatusList} msg The msg instance to transform. - * @return {!Object} - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltPeerStatusList.toObject = function(includeInstance, msg) { - var f, obj = { - peersList: jspb.Message.toObjectList(msg.getPeersList(), - proto.dlt.DltPeerStatus.toObject, includeInstance) - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.dlt.DltPeerStatusList} - */ -proto.dlt.DltPeerStatusList.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.dlt.DltPeerStatusList; - return proto.dlt.DltPeerStatusList.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.dlt.DltPeerStatusList} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.dlt.DltPeerStatusList} - */ -proto.dlt.DltPeerStatusList.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = new proto.dlt.DltPeerStatus; - reader.readMessage(value,proto.dlt.DltPeerStatus.deserializeBinaryFromReader); - msg.addPeers(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.dlt.DltPeerStatusList.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - proto.dlt.DltPeerStatusList.serializeBinaryToWriter(this, writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the given message to binary data (in protobuf wire - * format), writing to the given BinaryWriter. - * @param {!proto.dlt.DltPeerStatusList} message - * @param {!jspb.BinaryWriter} writer - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.dlt.DltPeerStatusList.serializeBinaryToWriter = function(message, writer) { - var f = undefined; - f = message.getPeersList(); - if (f.length > 0) { - writer.writeRepeatedMessage( - 1, - f, - proto.dlt.DltPeerStatus.serializeBinaryToWriter - ); - } -}; - - -/** - * repeated DltPeerStatus peers = 1; - * @return {!Array} - */ -proto.dlt.DltPeerStatusList.prototype.getPeersList = function() { - return /** @type{!Array} */ ( - jspb.Message.getRepeatedWrapperField(this, proto.dlt.DltPeerStatus, 1)); -}; - - -/** - * @param {!Array} value - * @return {!proto.dlt.DltPeerStatusList} returns this -*/ -proto.dlt.DltPeerStatusList.prototype.setPeersList = function(value) { - return jspb.Message.setRepeatedWrapperField(this, 1, value); -}; - - -/** - * @param {!proto.dlt.DltPeerStatus=} opt_value - * @param {number=} opt_index - * @return {!proto.dlt.DltPeerStatus} - */ -proto.dlt.DltPeerStatusList.prototype.addPeers = function(opt_value, opt_index) { - return jspb.Message.addToRepeatedWrapperField(this, 1, opt_value, proto.dlt.DltPeerStatus, opt_index); -}; - - -/** - * Clears the list making it empty but non-null. - * @return {!proto.dlt.DltPeerStatusList} returns this - */ -proto.dlt.DltPeerStatusList.prototype.clearPeersList = function() { - return this.setPeersList([]); -}; - - -/** - * @enum {number} - */ -proto.dlt.DltRecordTypeEnum = { - DLTRECORDTYPE_UNDEFINED: 0, - DLTRECORDTYPE_CONTEXT: 1, - DLTRECORDTYPE_TOPOLOGY: 2, - DLTRECORDTYPE_DEVICE: 3, - DLTRECORDTYPE_LINK: 4, - DLTRECORDTYPE_SERVICE: 5, - DLTRECORDTYPE_SLICE: 6 -}; - -/** - * @enum {number} - */ -proto.dlt.DltRecordOperationEnum = { - DLTRECORDOPERATION_UNDEFINED: 0, - DLTRECORDOPERATION_ADD: 1, - DLTRECORDOPERATION_UPDATE: 2, - DLTRECORDOPERATION_DELETE: 3 -}; - -/** - * @enum {number} - */ -proto.dlt.DltRecordStatusEnum = { - DLTRECORDSTATUS_UNDEFINED: 0, - DLTRECORDSTATUS_SUCCEEDED: 1, - DLTRECORDSTATUS_FAILED: 2 -}; - -/** - * @enum {number} - */ -proto.dlt.DltStatusEnum = { - DLTSTATUS_UNDEFINED: 0, - DLTSTATUS_NOTAVAILABLE: 1, - DLTSTATUS_INITIALIZED: 2, - DLTSTATUS_AVAILABLE: 3, - DLTSTATUS_DEINIT: 4 -}; - -goog.object.extend(exports, proto.dlt); diff --git a/src/dlt/gateway/dltApp/src/dltGateway.js b/src/dlt/gateway/dltApp/src/dltGateway.js index 374be6464..656397dc8 100644 --- a/src/dlt/gateway/dltApp/src/dltGateway.js +++ b/src/dlt/gateway/dltApp/src/dltGateway.js @@ -20,7 +20,7 @@ const { connectToNetwork } = require('../dist/fabricConnect'); const utf8Decoder = new TextDecoder(); // Load the protocol buffer definitions -const PROTO_PATH = path.resolve(__dirname, '../proto/dlt_service.proto'); +const PROTO_PATH = path.resolve(__dirname, '../proto/dlt_gateway.proto'); const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, diff --git a/src/dlt/gateway/dltApp/tests/testEvents.js b/src/dlt/gateway/dltApp/tests/testEvents.js index 2590d40d4..d788f04e1 100644 --- a/src/dlt/gateway/dltApp/tests/testEvents.js +++ b/src/dlt/gateway/dltApp/tests/testEvents.js @@ -17,7 +17,7 @@ const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const path = require('path'); -const PROTO_PATH = path.resolve(__dirname, '../proto/dlt_service.proto'); +const PROTO_PATH = path.resolve(__dirname, '../proto/dlt_gateway.proto'); const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, diff --git a/src/dlt/gateway/dltApp/tests/testGateway.js b/src/dlt/gateway/dltApp/tests/testGateway.js index 778c0f909..099837567 100644 --- a/src/dlt/gateway/dltApp/tests/testGateway.js +++ b/src/dlt/gateway/dltApp/tests/testGateway.js @@ -20,7 +20,7 @@ const fs = require('fs').promises; const { v4: uuidv4 } = require('uuid'); // Import the UUID library -const PROTO_PATH = path.resolve(__dirname, '../proto/dlt_service.proto'); +const PROTO_PATH = path.resolve(__dirname, '../proto/dlt_gateway.proto'); const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, -- GitLab From d2c607408c56d02a94915d39690b6738808a2df5 Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Tue, 10 Sep 2024 11:37:30 +0200 Subject: [PATCH 76/94] Code Cleanup --- src/dlt/gateway/dltApp/src/fabricConnect.ts | 28 ++++++++++++--------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/dlt/gateway/dltApp/src/fabricConnect.ts b/src/dlt/gateway/dltApp/src/fabricConnect.ts index 4bef9b335..85ad15b2e 100644 --- a/src/dlt/gateway/dltApp/src/fabricConnect.ts +++ b/src/dlt/gateway/dltApp/src/fabricConnect.ts @@ -21,27 +21,27 @@ import { TextDecoder } from 'util'; import * as dotenv from 'dotenv'; dotenv.config({ path: path.resolve(__dirname, '..', '.env') }); -const channelName = envOrDefault('CHANNEL_NAME', 'channel1'); -const chaincodeName = envOrDefault('CHAINCODE_NAME', 'adrenalineDLT'); -const mspId = envOrDefault('MSP_ID', 'Org1MSP'); +const channelName = getEnvVar('CHANNEL_NAME'); +const chaincodeName = getEnvVar('CHAINCODE_NAME'); +const mspId = getEnvVar('MSP_ID'); // Path to crypto materials. -const cryptoPath = envOrDefault('CRYPTO_PATH', path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.adrenaline.com')); +const cryptoPath = getEnvVar('CRYPTO_PATH'); // Path to user private key directory. -const keyDirectoryPath = envOrDefault('KEY_DIRECTORY_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.adrenaline.com', 'msp', 'keystore')); +const keyDirectoryPath = getEnvVar('KEY_DIRECTORY_PATH'); // Path to user certificate directory. -const certDirectoryPath = envOrDefault('CERT_DIRECTORY_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.adrenaline.com', 'msp', 'signcerts')); +const certDirectoryPath = getEnvVar('CERT_DIRECTORY_PATH'); // Path to peer tls certificate. -const tlsCertPath = envOrDefault('TLS_CERT_PATH', path.resolve(cryptoPath, 'peers', 'peer1.org1.adrenaline.com', 'tls', 'ca.crt')); +const tlsCertPath = getEnvVar('TLS_CERT_PATH'); // Gateway peer endpoint. -const peerEndpoint = envOrDefault('PEER_ENDPOINT', 'localhost:7051'); +const peerEndpoint = getEnvVar('PEER_ENDPOINT'); // Gateway peer SSL host name override. -const peerHostAlias = envOrDefault('PEER_HOST_ALIAS', 'peer1.org1.adrenaline.com'); +const peerHostAlias = getEnvVar('PEER_HOST_ALIAS'); const utf8Decoder = new TextDecoder(); const assetId = `asset${Date.now()}`; @@ -154,10 +154,14 @@ async function initLedger(contract: Contract): Promise { /** - * envOrDefault() will return the value of an environment variable, or a default value if the variable is undefined. + * getEnvVar() will return the value of an environment variable. */ -function envOrDefault(key: string, defaultValue: string): string { - return process.env[key] || defaultValue; +function getEnvVar(varName: string): string { + const value = process.env[varName]; + if (!value) { + throw new Error(`Environment variable ${varName} is not set`); + } + return value; } /** -- GitLab From 9927983157a73ececb2acaa9bfb826b57dde5dec Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Tue, 10 Sep 2024 11:57:59 +0200 Subject: [PATCH 77/94] Code Cleanup --- src/dlt/gateway/README.md | 47 +------------------ src/dlt/gateway/dltApp/package-lock.json | 4 +- src/dlt/gateway/dltApp/package.json | 6 +-- src/dlt/gateway/dltApp/tsconfig.json | 3 +- .../gateway/{dltApp => }/tests/perfTest.js | 6 +-- .../gateway/{dltApp => }/tests/rateTest.js | 2 +- .../gateway/{dltApp => }/tests/simpleTest.js | 2 +- .../gateway/{dltApp => }/tests/testEvents.js | 4 +- .../gateway/{dltApp => }/tests/testGateway.js | 4 +- 9 files changed, 17 insertions(+), 61 deletions(-) rename src/dlt/gateway/{dltApp => }/tests/perfTest.js (95%) rename src/dlt/gateway/{dltApp => }/tests/rateTest.js (97%) rename src/dlt/gateway/{dltApp => }/tests/simpleTest.js (97%) rename src/dlt/gateway/{dltApp => }/tests/testEvents.js (92%) rename src/dlt/gateway/{dltApp => }/tests/testGateway.js (96%) diff --git a/src/dlt/gateway/README.md b/src/dlt/gateway/README.md index 401e17864..0ea41cd2f 100644 --- a/src/dlt/gateway/README.md +++ b/src/dlt/gateway/README.md @@ -1,16 +1,8 @@ -# ADRENALINE DLT App - -
- DLT Gateway for chaincode interaction -
DLT_APP for chaincode tests.
-
+# DLT Gateway ## Description -The DLT app consists of a **fabricConnect.ts** TypeScript file, which contains the logic for identification management (certificates required for the MSP), connection management to the blockchain, and finally, it exposes a contract object with all the required information for interacting with the chaincode. The **fabricConnect.ts** is coded following the Fabric Gateway API recommendations from Hyperledger Fabric 2.4+. The compiled **fabricConnect.ts** logic is imported into a **dltGateway.js** file, which contains the gRPC logic for interaction with the TFS controller. Testing code for various performance tests is included inside the [/dltApp/tests](./dltApp/tests/) folder. +The DLT Gateway consists of a **fabricConnect.ts** TypeScript file, which contains the logic for identification management (certificates required for the MSP), connection management to the blockchain, and finally, it exposes a contract object with all the required information for interacting with the chaincode. The **fabricConnect.ts** is coded following the Fabric Gateway API recommendations from Hyperledger Fabric 2.4+. The compiled **fabricConnect.ts** logic is imported into a **dltGateway.js** file, which contains the gRPC logic for interaction with the TFS controller. Testing code for various performance tests is included inside the [/tests](./tests/) folder. The chaincode is written in Go, providing a reference for the operations that are recorded in the blockchain. This chaincode must already be deployed in a working Hyperledger Fabric blockchain. @@ -20,38 +12,3 @@ The chaincode is written in Go, providing a reference for the operations that ar * Docker * K8s -## Building the App - -Install the dependencies and compile the source code. - -```bash -npm install -``` - -## Packing the App - -The [automation](./automation/) folder contains the Dockerfiles and Kubernetes configuration files alongside deployment scripts. - -### Build a Docker Image - -Using the Dockerfile, create a Docker image of the Gateway. - -```bash -docker build -t your-username/dltgateway:v1.0.0 . -``` - -If necessary, upload the Docker image to a Docker repository for convenience. (You can work with the local image if deploying the Kubernetes service on the same machine.) - -```bash -docker push your-username/dltgateway:v1.0.0 -``` - -### Run the Deployment Script - -Make the necessary changes to the environment variables inside the **configmap.yaml** according to the specific Fabric deployment. Also, update the path in the **persistent-volume.yaml** with the correct information. - -```bash -./deploy_dlt_gateway.sh -``` - -Once the Kubernetes service is deployed, TFS can perform gRPC requests to as usual. \ No newline at end of file diff --git a/src/dlt/gateway/dltApp/package-lock.json b/src/dlt/gateway/dltApp/package-lock.json index 9ab326263..6fb245621 100644 --- a/src/dlt/gateway/dltApp/package-lock.json +++ b/src/dlt/gateway/dltApp/package-lock.json @@ -1,11 +1,11 @@ { - "name": "adrenalineDLT_APP", + "name": "dlt_gateway", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "adrenalineDLT_APP", + "name": "dlt_gateway", "version": "1.0.0", "license": "Apache-2.0", "dependencies": { diff --git a/src/dlt/gateway/dltApp/package.json b/src/dlt/gateway/dltApp/package.json index 58ec22dea..5eabc2dff 100644 --- a/src/dlt/gateway/dltApp/package.json +++ b/src/dlt/gateway/dltApp/package.json @@ -1,7 +1,7 @@ { - "name": "adrenalineDLT_APP", + "name": "dlt_gateway", "version": "1.0.0", - "description": "A DLT application that record and manages topology data as JSON implemented in typeScript using fabric-gateway", + "description": "A DLT application that record and manages network related data as JSON, implemented in typeScript using HLF fabric-gateway", "main": "dist/index.js", "typings": "dist/index.d.ts", "engines": { @@ -13,7 +13,7 @@ "lint": "eslint . --ext .ts", "prepare": "npm run build", "pretest": "npm run lint", - "start": "node dist/adrenalineDLT_app.js" + "start": "node dist/dlt_gateway.js" }, "engineStrict": true, "author": "CTTC-Javier", diff --git a/src/dlt/gateway/dltApp/tsconfig.json b/src/dlt/gateway/dltApp/tsconfig.json index c81e4d5f9..34eddef69 100644 --- a/src/dlt/gateway/dltApp/tsconfig.json +++ b/src/dlt/gateway/dltApp/tsconfig.json @@ -9,8 +9,7 @@ "noImplicitAny": true }, "include": [ - "./src/**/*" -, "src.old/grpcClient.js", "src.old/grpcServer.js", "tests/testEvents.js", "tests/testGateway.js" ], + "./src/**/*" ], "exclude": [ "./src/**/*.spec.ts" ] diff --git a/src/dlt/gateway/dltApp/tests/perfTest.js b/src/dlt/gateway/tests/perfTest.js similarity index 95% rename from src/dlt/gateway/dltApp/tests/perfTest.js rename to src/dlt/gateway/tests/perfTest.js index 6549d4e7b..fd60bd9c4 100644 --- a/src/dlt/gateway/dltApp/tests/perfTest.js +++ b/src/dlt/gateway/tests/perfTest.js @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -const { connectToNetwork } = require('../dist/fabricConnect'); +const { connectToNetwork } = require('../dltApp/dist/fabricConnect'); const fsp = require('fs').promises; const fs = require('fs'); const util = require('util'); const utf8Decoder = new TextDecoder(); const topoDirectory = '../samples/'; -//const topologies = ['topo1.json', 'topo2.json', 'topo3.json', 'topo4.json']; -const topologies = ['topo4.json']; +const topologies = ['topo1.json', 'topo2.json', 'topo3.json', 'topo4.json']; +//const topologies = ['topo4.json']; const iterations = 1000; diff --git a/src/dlt/gateway/dltApp/tests/rateTest.js b/src/dlt/gateway/tests/rateTest.js similarity index 97% rename from src/dlt/gateway/dltApp/tests/rateTest.js rename to src/dlt/gateway/tests/rateTest.js index ca3f540d3..f7c408842 100644 --- a/src/dlt/gateway/dltApp/tests/rateTest.js +++ b/src/dlt/gateway/tests/rateTest.js @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -const { connectToNetwork } = require('../dist/fabricConnect'); +const { connectToNetwork } = require('../dltApp/dist/fabricConnect'); const fs = require('fs'); const util = require('util'); const appendFile = util.promisify(fs.appendFile); diff --git a/src/dlt/gateway/dltApp/tests/simpleTest.js b/src/dlt/gateway/tests/simpleTest.js similarity index 97% rename from src/dlt/gateway/dltApp/tests/simpleTest.js rename to src/dlt/gateway/tests/simpleTest.js index d84b5dba1..72da03dec 100644 --- a/src/dlt/gateway/dltApp/tests/simpleTest.js +++ b/src/dlt/gateway/tests/simpleTest.js @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -const { connectToNetwork } = require('../dist/fabricConnect'); +const { connectToNetwork } = require('../dltApp/dist/fabricConnect'); const fs = require('fs'); const util = require('util'); const appendFile = util.promisify(fs.appendFile); diff --git a/src/dlt/gateway/dltApp/tests/testEvents.js b/src/dlt/gateway/tests/testEvents.js similarity index 92% rename from src/dlt/gateway/dltApp/tests/testEvents.js rename to src/dlt/gateway/tests/testEvents.js index d788f04e1..8d209723e 100644 --- a/src/dlt/gateway/dltApp/tests/testEvents.js +++ b/src/dlt/gateway/tests/testEvents.js @@ -17,7 +17,7 @@ const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const path = require('path'); -const PROTO_PATH = path.resolve(__dirname, '../proto/dlt_gateway.proto'); +const PROTO_PATH = path.resolve(__dirname, '../../../../proto/dlt_gateway.proto'); const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, @@ -28,7 +28,7 @@ const packageDefinition = protoLoader.loadSync(PROTO_PATH, { const dltProto = grpc.loadPackageDefinition(packageDefinition).dlt; const client = new dltProto.DltGatewayService( - '10.1.1.96:32001', + '10.1.1.96:32001', //Replace with TFS server IP_ADDRESS grpc.credentials.createInsecure() ); diff --git a/src/dlt/gateway/dltApp/tests/testGateway.js b/src/dlt/gateway/tests/testGateway.js similarity index 96% rename from src/dlt/gateway/dltApp/tests/testGateway.js rename to src/dlt/gateway/tests/testGateway.js index 099837567..dde6b3efc 100644 --- a/src/dlt/gateway/dltApp/tests/testGateway.js +++ b/src/dlt/gateway/tests/testGateway.js @@ -20,7 +20,7 @@ const fs = require('fs').promises; const { v4: uuidv4 } = require('uuid'); // Import the UUID library -const PROTO_PATH = path.resolve(__dirname, '../proto/dlt_gateway.proto'); +const PROTO_PATH = path.resolve(__dirname, '../../../../proto/dlt_gateway.proto'); const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, @@ -40,7 +40,7 @@ const domainUuid = `domain-${uuidv4()}`; // Generate a pretty domain UUID async function getTopoData(filename) { try { - const data = await fs.readFile(`../../samples/${filename}`, 'utf8'); + const data = await fs.readFile(`../samples/${filename}`, 'utf8'); return data; } catch (error) { console.error('Failed to read file:', filename, error); -- GitLab From ed1b4bbee442a2b1a3bb4dfdde331c0c6b2b1a54 Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Tue, 10 Sep 2024 12:06:06 +0200 Subject: [PATCH 78/94] Code Cleanup --- src/dlt/connector/client/DltEventsCollector.py | 2 +- src/dlt/connector/service/DltConnectorServiceServicerImpl.py | 2 +- .../connector/service/event_dispatcher/DltEventDispatcher.py | 4 ++-- src/dlt/performance/__main__.py | 2 +- src/dlt/performance/play_ground/Dlt.py | 2 +- src/dlt/performance/play_ground/__init__.py | 2 +- src/interdomain/service/InterdomainServiceServicerImpl.py | 2 +- .../service/topology_abstractor/DltRecordSender.py | 2 +- .../service/topology_abstractor/TopologyAbstractor.py | 2 +- src/load_generator/load_gen/DltTools.py | 2 +- src/load_generator/load_gen/RequestGenerator.py | 2 +- src/load_generator/tests/test_dlt_functional.py | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/dlt/connector/client/DltEventsCollector.py b/src/dlt/connector/client/DltEventsCollector.py index 594d84838..e59784a4d 100644 --- a/src/dlt/connector/client/DltEventsCollector.py +++ b/src/dlt/connector/client/DltEventsCollector.py @@ -16,7 +16,7 @@ from typing import Callable, Optional import grpc, logging, queue, threading, time from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordSubscription from common.tools.grpc.Tools import grpc_message_to_json_string -from src.dlt.connector.client.DltGatewayClient import DltGatewayClient +from dlt.connector.client.DltGatewayClient import DltGatewayClient LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) diff --git a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py index 17cc6fd33..1d4066fc9 100644 --- a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py +++ b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py @@ -22,7 +22,7 @@ from common.proto.dlt_connector_pb2_grpc import DltConnectorServiceServicer from common.proto.dlt_gateway_pb2 import DltRecord, DltRecordId, DltRecordOperationEnum, DltRecordTypeEnum from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient -from src.dlt.connector.client.DltGatewayClientAsync import DltGatewayClientAsync +from dlt.connector.client.DltGatewayClientAsync import DltGatewayClientAsync from .tools.Checkers import record_exists LOGGER = logging.getLogger(__name__) diff --git a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py index 4ae6fec54..779bae9c1 100644 --- a/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py +++ b/src/dlt/connector/service/event_dispatcher/DltEventDispatcher.py @@ -26,9 +26,9 @@ from common.tools.grpc.Tools import grpc_message_to_json_string from common.tools.object_factory.Context import json_context_id from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient -from src.dlt.connector.client.DltConnectorClient import DltConnectorClient +from dlt.connector.client.DltConnectorClient import DltConnectorClient from dlt.connector.client.DltEventsCollector import DltEventsCollector -from src.dlt.connector.client.DltGatewayClient import DltGatewayClient +from dlt.connector.client.DltGatewayClient import DltGatewayClient from interdomain.client.InterdomainClient import InterdomainClient LOGGER = logging.getLogger(__name__) diff --git a/src/dlt/performance/__main__.py b/src/dlt/performance/__main__.py index 09248c1b1..1d6c41965 100644 --- a/src/dlt/performance/__main__.py +++ b/src/dlt/performance/__main__.py @@ -14,7 +14,7 @@ import functools, logging, pathlib, sys, time from common.proto.dlt_gateway_pb2 import DltRecordEvent -from src.dlt.connector.client.DltGatewayClient import DltGatewayClient +from dlt.connector.client.DltGatewayClient import DltGatewayClient from dlt.connector.client.DltEventsCollector import DltEventsCollector from .play_ground.Enums import CONTEXT_EVENT_TYPE_TO_ACTION, RECORD_TYPE_TO_ENUM from .play_ground import PlayGround diff --git a/src/dlt/performance/play_ground/Dlt.py b/src/dlt/performance/play_ground/Dlt.py index 5de186c8c..b2c889545 100644 --- a/src/dlt/performance/play_ground/Dlt.py +++ b/src/dlt/performance/play_ground/Dlt.py @@ -18,7 +18,7 @@ from common.proto.context_pb2 import Device, Link, Service, Slice from common.proto.dlt_gateway_pb2 import ( DltRecord, DltRecordId, DltRecordOperationEnum, DltRecordStatus, DltRecordTypeEnum) from common.tools.grpc.Tools import grpc_message_to_json_string -from src.dlt.connector.client.DltGatewayClient import DltGatewayClient +from dlt.connector.client.DltGatewayClient import DltGatewayClient from .PerfPoint import PerfPoint DLT_OPERATION_CREATE = DltRecordOperationEnum.DLTRECORDOPERATION_ADD diff --git a/src/dlt/performance/play_ground/__init__.py b/src/dlt/performance/play_ground/__init__.py index ce34d30a4..68ad14dee 100644 --- a/src/dlt/performance/play_ground/__init__.py +++ b/src/dlt/performance/play_ground/__init__.py @@ -15,7 +15,7 @@ import logging, operator, random from typing import Dict, Tuple from common.proto.context_pb2 import Device, Link, Service, Slice -from src.dlt.connector.client.DltGatewayClient import DltGatewayClient +from dlt.connector.client.DltGatewayClient import DltGatewayClient from .Enums import ActionEnum, RecordTypeEnum from .Dlt import ( DLT_OPERATION_CREATE, DLT_OPERATION_DELETE, DLT_OPERATION_UPDATE, diff --git a/src/interdomain/service/InterdomainServiceServicerImpl.py b/src/interdomain/service/InterdomainServiceServicerImpl.py index 146284aaa..bce5e6920 100644 --- a/src/interdomain/service/InterdomainServiceServicerImpl.py +++ b/src/interdomain/service/InterdomainServiceServicerImpl.py @@ -34,7 +34,7 @@ from common.tools.object_factory.Device import json_device_id from common.tools.object_factory.EndPoint import json_endpoint_id from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient -from src.dlt.connector.client.DltConnectorClientAsync import DltConnectorClientAsync +from dlt.connector.client.DltConnectorClientAsync import DltConnectorClientAsync from pathcomp.frontend.client.PathCompClient import PathCompClient from service.client.ServiceClient import ServiceClient from slice.client.SliceClient import SliceClient diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index 104f2e378..2dd899be2 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -21,7 +21,7 @@ from common.proto.context_pb2 import Device, Link, Service, Slice, TopologyId from common.proto.dlt_connector_pb2 import DltDeviceId, DltLinkId, DltServiceId, DltSliceId from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient -from src.dlt.connector.client.DltConnectorClientAsync import DltConnectorClientAsync +from dlt.connector.client.DltConnectorClientAsync import DltConnectorClientAsync LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) diff --git a/src/interdomain/service/topology_abstractor/TopologyAbstractor.py b/src/interdomain/service/topology_abstractor/TopologyAbstractor.py index 0dbb332db..2a6ef5e32 100644 --- a/src/interdomain/service/topology_abstractor/TopologyAbstractor.py +++ b/src/interdomain/service/topology_abstractor/TopologyAbstractor.py @@ -33,7 +33,7 @@ from common.tools.object_factory.Device import json_device_id from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient from context.client.EventsCollector import EventsCollector -from src.dlt.connector.client.DltConnectorClient import DltConnectorClient +from dlt.connector.client.DltConnectorClient import DltConnectorClient from .AbstractDevice import AbstractDevice from .AbstractLink import AbstractLink from .DltRecordSender import DltRecordSender diff --git a/src/load_generator/load_gen/DltTools.py b/src/load_generator/load_gen/DltTools.py index 8321dffa5..0ac7ab3f0 100644 --- a/src/load_generator/load_gen/DltTools.py +++ b/src/load_generator/load_gen/DltTools.py @@ -19,7 +19,7 @@ from common.proto.dlt_connector_pb2 import DltDeviceId, DltLinkId, DltServiceId, from common.tools.grpc.Tools import grpc_message_to_json_string from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient -from src.dlt.connector.client.DltConnectorClient import DltConnectorClient +from dlt.connector.client.DltConnectorClient import DltConnectorClient def explore_entities_to_record( slice_id : Optional[SliceId] = None, service_id : Optional[ServiceId] = None diff --git a/src/load_generator/load_gen/RequestGenerator.py b/src/load_generator/load_gen/RequestGenerator.py index e6e934b68..2a3e89fe0 100644 --- a/src/load_generator/load_gen/RequestGenerator.py +++ b/src/load_generator/load_gen/RequestGenerator.py @@ -27,7 +27,7 @@ from common.tools.object_factory.Service import ( from common.tools.object_factory.Slice import json_slice from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient -from src.dlt.connector.client.DltConnectorClient import DltConnectorClient +from dlt.connector.client.DltConnectorClient import DltConnectorClient from load_generator.tools.ListScalarRange import generate_value from .Constants import ENDPOINT_COMPATIBILITY, RequestType from .DltTools import record_device_to_dlt, record_link_to_dlt diff --git a/src/load_generator/tests/test_dlt_functional.py b/src/load_generator/tests/test_dlt_functional.py index 588200e0c..f3dea3b4a 100644 --- a/src/load_generator/tests/test_dlt_functional.py +++ b/src/load_generator/tests/test_dlt_functional.py @@ -19,7 +19,7 @@ from common.proto.dlt_connector_pb2 import DltDeviceId, DltLinkId, DltServiceId, from common.tools.object_factory.Device import json_device from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient -from src.dlt.connector.client.DltConnectorClient import DltConnectorClient +from dlt.connector.client.DltConnectorClient import DltConnectorClient def record_device_to_dlt( dlt_connector_client : DltConnectorClient, domain_id : TopologyId, device_id : DeviceId, delete : bool = False -- GitLab From a83ab192971236c52a103367a920ad38925e9609 Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Tue, 10 Sep 2024 12:11:01 +0200 Subject: [PATCH 79/94] Code Cleanup --- src/dlt/gateway/Dockerfile | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile index 66b7e0b30..f277aa05b 100644 --- a/src/dlt/gateway/Dockerfile +++ b/src/dlt/gateway/Dockerfile @@ -1,3 +1,17 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # Use an official Node.js runtime as a parent image FROM node:20 -- GitLab From c45dd3fe81605f4d9af86337c5fa3b30efb67199 Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Mon, 16 Sep 2024 10:59:01 +0200 Subject: [PATCH 80/94] Code Cleanup --- manifests/dltservice.yaml | 44 +++++--------------------------------- src/dlt/gateway/Dockerfile | 11 +++++++--- 2 files changed, 13 insertions(+), 42 deletions(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index 04f0921ca..64d74188b 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -17,11 +17,11 @@ kind: ConfigMap metadata: name: dlt-config data: - CHANNEL_NAME: "channel1" - CHAINCODE_NAME: "adrenalineDLT" - MSP_ID: "Org1MSP" - PEER_ENDPOINT: "10.1.1.96:7051" #Change to required peer# - PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" + CHANNEL_NAME: "channel1" #Change according to your blockchain configuration + CHAINCODE_NAME: "adrenalineDLT" #Change according to your blockchain configuration + MSP_ID: "Org1MSP" #Change according to your blockchain configuration + PEER_ENDPOINT: "10.1.1.96:7051" #Change to required peer address according to your blockchain deployment# + PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" #Change according to your blockchain configuration CRYPTO_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com" KEY_DIRECTORY_PATH: "/etc/hyperledger/fabric-keystore/keystore" CERT_DIRECTORY_PATH: "/etc/hyperledger/fabric-signcerts/signcerts.pem" @@ -83,9 +83,6 @@ spec: cpu: 700m memory: 1024Mi volumeMounts: - - mountPath: /test-network - name: dlt-volume - readOnly: true - name: keystore mountPath: /etc/hyperledger/fabric-keystore readOnly: true @@ -133,9 +130,6 @@ spec: - name: TLS_CERT_PATH value: "/etc/hyperledger/fabric-ca-crt/ca.crt" volumes: - - name: dlt-volume - persistentVolumeClaim: - claimName: dlt-pvc - name: keystore secret: secretName: dlt-keystone @@ -146,34 +140,6 @@ spec: secret: secretName: dlt-ca-crt ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: dlt-pvc -spec: - accessModes: - - ReadOnlyMany - resources: - requests: - storage: 1Gi - ---- -apiVersion: v1 -kind: PersistentVolume -metadata: - name: dlt-pv -spec: - capacity: - storage: 1Gi - accessModes: - - ReadOnlyMany - persistentVolumeReclaimPolicy: Retain - hostPath: - path: "/home/ubuntu/fabric-samples/test-network" #Update to correct host paths where the MSP is located. - claimRef: - name: dlt-pvc - --- apiVersion: v1 kind: Service diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile index f277aa05b..78e735c09 100644 --- a/src/dlt/gateway/Dockerfile +++ b/src/dlt/gateway/Dockerfile @@ -18,14 +18,19 @@ FROM node:20 # Set the working directory in the container WORKDIR /usr/dltApp +# Create proto directory before copying the .proto files +RUN mkdir -p ./proto + # Copy package.json and package-lock.json COPY src/dlt/gateway/dltApp/package*.json ./ # Copy tsconfig.json COPY src/dlt/gateway/dltApp/tsconfig*.json ./ -# Copy the proto folder -COPY proto/context.proto ./proto -COPY proto/dlt_gateway.proto ./proto +# Copy the proto folder contents +COPY proto/acl.proto ./proto/acl.proto +COPY proto/kpi_sample_types.proto ./proto/kpi_sample_types.proto +COPY proto/context.proto ./proto/context.proto +COPY proto/dlt_gateway.proto ./proto/dlt_gateway.proto # Copy the src folder COPY src/dlt/gateway/dltApp/src/ ./src -- GitLab From 1573e9cc9c8d2f4bb095dd8884c2260d7a39acd9 Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Mon, 16 Sep 2024 11:10:10 +0200 Subject: [PATCH 81/94] Code Cleanup --- manifests/dltservice.yaml | 34 ++------------------- src/dlt/gateway/dltApp/src/fabricConnect.ts | 13 ++------ 2 files changed, 5 insertions(+), 42 deletions(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index 64d74188b..e96896489 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -22,7 +22,6 @@ data: MSP_ID: "Org1MSP" #Change according to your blockchain configuration PEER_ENDPOINT: "10.1.1.96:7051" #Change to required peer address according to your blockchain deployment# PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" #Change according to your blockchain configuration - CRYPTO_PATH: "/test-network/organizations/peerOrganizations/org1.adrenaline.com" KEY_DIRECTORY_PATH: "/etc/hyperledger/fabric-keystore/keystore" CERT_DIRECTORY_PATH: "/etc/hyperledger/fabric-signcerts/signcerts.pem" TLS_CERT_PATH: "/etc/hyperledger/fabric-ca-crt/ca.crt" @@ -92,37 +91,10 @@ spec: - name: ca-crt mountPath: /etc/hyperledger/fabric-ca-crt readOnly: true + envFrom: + - configMapRef: + name: dlt-config env: - - name: CHANNEL_NAME - valueFrom: - configMapKeyRef: - name: dlt-config - key: CHANNEL_NAME - - name: CHAINCODE_NAME - valueFrom: - configMapKeyRef: - name: dlt-config - key: CHAINCODE_NAME - - name: MSP_ID - valueFrom: - configMapKeyRef: - name: dlt-config - key: MSP_ID - - name: PEER_ENDPOINT - valueFrom: - configMapKeyRef: - name: dlt-config - key: PEER_ENDPOINT - - name: PEER_HOST_ALIAS - valueFrom: - configMapKeyRef: - name: dlt-config - key: PEER_HOST_ALIAS - - name: CRYPTO_PATH - valueFrom: - configMapKeyRef: - name: dlt-config - key: CRYPTO_PATH - name: KEY_DIRECTORY_PATH value: "/etc/hyperledger/fabric-keystore/keystore" - name: CERT_DIRECTORY_PATH diff --git a/src/dlt/gateway/dltApp/src/fabricConnect.ts b/src/dlt/gateway/dltApp/src/fabricConnect.ts index 85ad15b2e..973fd6077 100644 --- a/src/dlt/gateway/dltApp/src/fabricConnect.ts +++ b/src/dlt/gateway/dltApp/src/fabricConnect.ts @@ -25,8 +25,6 @@ const channelName = getEnvVar('CHANNEL_NAME'); const chaincodeName = getEnvVar('CHAINCODE_NAME'); const mspId = getEnvVar('MSP_ID'); -// Path to crypto materials. -const cryptoPath = getEnvVar('CRYPTO_PATH'); // Path to user private key directory. const keyDirectoryPath = getEnvVar('KEY_DIRECTORY_PATH'); @@ -114,21 +112,15 @@ async function newGrpcConnection(): Promise { async function newIdentity(): Promise { //const certPath = await getFirstDirFileName(certDirectoryPath); - console.log("DEBUG", certDirectoryPath); + //console.log("DEBUG", certDirectoryPath); const credentials = await fs.readFile(certDirectoryPath); return { mspId, credentials }; } -//async function getFirstDirFileName(dirPath: string): Promise { - // const files = await fs.readdir(dirPath); - // const filePath = path.join(dirPath, files[0]); - // const realFilePath = await fs.readlink(filePath); - // return path.join(dirPath, realFilePath); -//} async function newSigner(): Promise { //const keyPath = await getFirstDirFileName(keyDirectoryPath); - console.log("DEBUG2", keyDirectoryPath); + //console.log("DEBUG2", keyDirectoryPath); const privateKeyPem = await fs.readFile(keyDirectoryPath); const privateKey = crypto.createPrivateKey(privateKeyPem); return signers.newPrivateKeySigner(privateKey); @@ -171,7 +163,6 @@ async function displayInputParameters(): Promise { console.log(`channelName: ${channelName}`); console.log(`chaincodeName: ${chaincodeName}`); console.log(`mspId: ${mspId}`); - console.log(`cryptoPath: ${cryptoPath}`); console.log(`keyDirectoryPath: ${keyDirectoryPath}`); console.log(`certDirectoryPath: ${certDirectoryPath}`); console.log(`tlsCertPath: ${tlsCertPath}`); -- GitLab From fe4af7af920c4d7ecccc930728011fa146e4d246 Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Mon, 16 Sep 2024 11:28:41 +0200 Subject: [PATCH 82/94] Code Cleanup --- deploy/tfs.sh | 36 ++++++++++++------------------------ my_deploy.sh | 9 ++++++++- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/deploy/tfs.sh b/deploy/tfs.sh index 26ed52f82..293a56f0f 100755 --- a/deploy/tfs.sh +++ b/deploy/tfs.sh @@ -118,20 +118,6 @@ export PROM_EXT_PORT_HTTP=${PROM_EXT_PORT_HTTP:-"9090"} # If not already set, set the external port Grafana HTTP Dashboards will be exposed to. export GRAF_EXT_PORT_HTTP=${GRAF_EXT_PORT_HTTP:-"3000"} -# ----- HLF Key Paths ----------------------------------------------------------- - -echo "Keystore PATH" -KEY_DIRECTORY_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/keystore/priv_sk" -printf "\n" - -echo "signcerts PATH" -CERT_DIRECTORY_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/signcerts/User1@org1.adrenaline.com-cert.pem" -printf "\n" - -echo "ca.crt PATH" -TLS_CERT_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/peers/peer0.org1.adrenaline.com/tls/ca.crt" -printf "\n" - ######################################################################################################################## # Automated steps start here ######################################################################################################################## @@ -204,18 +190,20 @@ kubectl create secret generic qdb-data --namespace ${TFS_K8S_NAMESPACE} --type=' --from-literal=METRICSDB_PASSWORD=${QDB_PASSWORD} printf "\n" -echo "Create secret for HLF keystore" -kubectl create secret generic dlt-keystone --namespace ${TFS_K8S_NAMESPACE} --from-file=keystore=${KEY_DIRECTORY_PATH} -printf "\n" - -echo "Create secret for HLF signcerts" -kubectl create secret generic dlt-signcerts --namespace ${TFS_K8S_NAMESPACE} --from-file=signcerts.pem=${CERT_DIRECTORY_PATH} -printf "\n" +# Check if "dlt" is in the list of components +if [[ " ${TFS_COMPONENTS[@]} " =~ " dlt " ]]; then + echo "Create secret for HLF keystore" + kubectl create secret generic dlt-keystone --namespace ${TFS_K8S_NAMESPACE} --from-file=keystore=${KEY_DIRECTORY_PATH} + printf "\n" -echo "Create secret for HLF ca.crt" -kubectl create secret generic dlt-ca-crt --namespace ${TFS_K8S_NAMESPACE} --from-file=ca.crt=${TLS_CERT_PATH} -printf "\n" + echo "Create secret for HLF signcerts" + kubectl create secret generic dlt-signcerts --namespace ${TFS_K8S_NAMESPACE} --from-file=signcerts.pem=${CERT_DIRECTORY_PATH} + printf "\n" + echo "Create secret for HLF ca.crt" + kubectl create secret generic dlt-ca-crt --namespace ${TFS_K8S_NAMESPACE} --from-file=ca.crt=${TLS_CERT_PATH} + printf "\n" +fi echo "Deploying components and collecting environment variables..." ENV_VARS_SCRIPT=tfs_runtime_env_vars.sh diff --git a/my_deploy.sh b/my_deploy.sh index bb1816bb9..c477aef1f 100755 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -20,7 +20,7 @@ export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/" # Set the list of components, separated by spaces, you want to build images for, and deploy. -export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_generator interdomain dlt" +export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_generator" # Uncomment to activate Monitoring (old) #export TFS_COMPONENTS="${TFS_COMPONENTS} monitoring" @@ -62,6 +62,13 @@ export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_gene # Uncomment to activate E2E Orchestrator #export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" +# Uncomment to activate DLT +export TFS_COMPONENTS="${TFS_COMPONENTS} interdomain dlt" +export KEY_DIRECTORY_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/keystore/priv_sk" +export CERT_DIRECTORY_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/signcerts/User1@org1.adrenaline.com-cert.pem" +export TLS_CERT_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/peers/peer0.org1.adrenaline.com/tls/ca.crt" + + # Set the tag you want to use for your images. export TFS_IMAGE_TAG="dev" -- GitLab From 534ad49c336e26cd350fb38721978bf9519dd2ae Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Mon, 16 Sep 2024 12:28:32 +0200 Subject: [PATCH 83/94] Code Cleanup --- manifests/dltservice.yaml | 10 +++++----- .../service/DltConnectorServiceServicerImpl.py | 2 +- src/dlt/connector/service/__main__.py | 8 ++++---- src/dlt/connector/service/tools/Checkers.py | 2 +- src/interdomain/service/__main__.py | 4 ++-- .../service/topology_abstractor/DltRecordSender.py | 3 +-- .../service/topology_abstractor/DltRecorder.py | 12 ++---------- 7 files changed, 16 insertions(+), 25 deletions(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index e96896489..35ab9919d 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -51,11 +51,11 @@ spec: env: - name: LOG_LEVEL value: "INFO" - ## for debug purposes - #- name: DLT_GATEWAY_HOST - # value: "mock-blockchain.tfs-bchain.svc.cluster.local" - #- name: DLT_GATEWAY_PORT - # value: "50051" + ## for debug purposes + #- name: DLT_GATEWAY_HOST + # value: "mock-blockchain.tfs-bchain.svc.cluster.local" + #- name: DLT_GATEWAY_PORT + # value: "50051" readinessProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:8080"] diff --git a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py index 1d4066fc9..edba01fac 100644 --- a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py +++ b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc, asyncio, logging +import logging from grpc.aio import ServicerContext from typing import Optional from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method_async diff --git a/src/dlt/connector/service/__main__.py b/src/dlt/connector/service/__main__.py index f8296660e..e368f6302 100644 --- a/src/dlt/connector/service/__main__.py +++ b/src/dlt/connector/service/__main__.py @@ -13,7 +13,7 @@ # limitations under the License. -import logging, signal, sys, threading, asyncio +import logging, signal, threading, asyncio from prometheus_client import start_http_server from common.Constants import ServiceNameEnum from common.Settings import ( @@ -25,12 +25,12 @@ from .DltConnectorService import DltConnectorService terminate = threading.Event() LOGGER: logging.Logger = None -def signal_handler(signal, frame): +def signal_handler(signal, frame): # pylint: disable=redefined-outer-name LOGGER.warning('Terminate signal received') terminate.set() async def main(): - global LOGGER + global LOGGER # pylint: disable=global-statement log_level = get_log_level() logging.basicConfig(level=log_level, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s") @@ -59,7 +59,7 @@ async def main(): await grpc_service.start() # Wait for Ctrl+C or termination signal - while not terminate.wait(timeout=1.0): + while not terminate.is_set(): await asyncio.sleep(1.0) LOGGER.info('Terminating...') diff --git a/src/dlt/connector/service/tools/Checkers.py b/src/dlt/connector/service/tools/Checkers.py index 94a10d409..9afb0da07 100644 --- a/src/dlt/connector/service/tools/Checkers.py +++ b/src/dlt/connector/service/tools/Checkers.py @@ -20,5 +20,5 @@ def record_exists(record : DltRecord) -> bool: exists = exists and (record.record_id.type != DLTRECORDTYPE_UNDEFINED) exists = exists and (len(record.record_id.record_uuid.uuid) > 0) #exists = exists and (record.operation != DLTRECORDOPERATION_UNDEFINED) - #exists = exists and (len(record.data_json) > 0) + #exists = exists and (len(record.data_json) > 0) #It conflicts as sometimes records do not have a data_json. return exists diff --git a/src/interdomain/service/__main__.py b/src/interdomain/service/__main__.py index 8c392821e..4181fa73a 100644 --- a/src/interdomain/service/__main__.py +++ b/src/interdomain/service/__main__.py @@ -50,7 +50,7 @@ def main(): signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) - LOGGER.info('Starting Interdomain Service...') + LOGGER.info('Starting...') # Start metrics server metrics_port = get_metrics_port() @@ -71,7 +71,7 @@ def main(): # topology_abstractor.start() # Subscribe to Context Events - #dlt_enabled = is_dlt_enabled() + #dlt_enabled = is_dlt_enabled() #How to change the config? dlt_enabled = True if dlt_enabled: LOGGER.info('Starting DLT functionality...') diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index 2dd899be2..5605ae412 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -16,10 +16,9 @@ import logging import asyncio -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Tuple from common.proto.context_pb2 import Device, Link, Service, Slice, TopologyId from common.proto.dlt_connector_pb2 import DltDeviceId, DltLinkId, DltServiceId, DltSliceId -from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient from dlt.connector.client.DltConnectorClientAsync import DltConnectorClientAsync diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 97b48628a..79067072c 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -12,21 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging -import threading -import asyncio -import time +import logging, threading, asyncio, time from typing import Dict, Optional - from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum -from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name -from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, EndPointId, Link, LinkId, LinkEvent, TopologyId, TopologyEvent +from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, LinkId, LinkEvent, TopologyId, TopologyEvent from common.tools.context_queries.Context import create_context -from common.tools.context_queries.Device import get_uuids_of_devices_in_topology -from common.tools.context_queries.Topology import create_missing_topologies from common.tools.grpc.Tools import grpc_message_to_json_string from common.tools.object_factory.Context import json_context_id -from common.tools.object_factory.Device import json_device_id from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient from context.client.EventsCollector import EventsCollector -- GitLab From d8ea4a412f497404919cd77c54e46bbfc006e50f Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Mon, 16 Sep 2024 12:43:26 +0200 Subject: [PATCH 84/94] Code Cleanup --- my_deploy.sh | 6 +++--- src/dlt/gateway/keys/.gitignore | 3 +++ src/dlt/gateway/keys/place_hls_keys_in_this_folder | 0 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 src/dlt/gateway/keys/.gitignore create mode 100644 src/dlt/gateway/keys/place_hls_keys_in_this_folder diff --git a/my_deploy.sh b/my_deploy.sh index c477aef1f..0fb205542 100755 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -64,9 +64,9 @@ export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_gene # Uncomment to activate DLT export TFS_COMPONENTS="${TFS_COMPONENTS} interdomain dlt" -export KEY_DIRECTORY_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/keystore/priv_sk" -export CERT_DIRECTORY_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/users/User1@org1.adrenaline.com/msp/signcerts/User1@org1.adrenaline.com-cert.pem" -export TLS_CERT_PATH="${HOME}/fabric-samples/test-network/organizations/peerOrganizations/org1.adrenaline.com/peers/peer0.org1.adrenaline.com/tls/ca.crt" +export KEY_DIRECTORY_PATH="src/dlt/gateway/keys/priv_sk" +export CERT_DIRECTORY_PATH="src/dlt/gateway/keys/cert.pem" +export TLS_CERT_PATH="src/dlt/gateway/keys/ca.crt" # Set the tag you want to use for your images. diff --git a/src/dlt/gateway/keys/.gitignore b/src/dlt/gateway/keys/.gitignore new file mode 100644 index 000000000..28d89119f --- /dev/null +++ b/src/dlt/gateway/keys/.gitignore @@ -0,0 +1,3 @@ +ca.crt +cert.pem +priv_sk \ No newline at end of file diff --git a/src/dlt/gateway/keys/place_hls_keys_in_this_folder b/src/dlt/gateway/keys/place_hls_keys_in_this_folder new file mode 100644 index 000000000..e69de29bb -- GitLab From e9d0bafc1f1e862c6d02075f3764435f7aeb5246 Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Mon, 16 Sep 2024 12:54:33 +0200 Subject: [PATCH 85/94] Code Cleanup --- src/dlt/gateway/README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/dlt/gateway/README.md b/src/dlt/gateway/README.md index 0ea41cd2f..ae05ed6c6 100644 --- a/src/dlt/gateway/README.md +++ b/src/dlt/gateway/README.md @@ -10,5 +10,18 @@ The chaincode is written in Go, providing a reference for the operations that ar * NodeJS * Docker -* K8s +* Kubernetes (K8s) + +Sign and TLS certificates, and private key of the MSP user from the Hyperledger Fabric deployment must be copied to the [/keys](./keys/) directory inside this repository. + Example: + + ```bash + cp ~/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/tls/ca.crt src/dlt/gateway/keys/ + + cp ~/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem src/dlt/gateway/keys/cert.pem + + cp ~/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv_sk src/dlt/gateway/keys/ + ``` + +These files are essential for establishing the identity and secure connection to the blockchain. Make sure you replace the paths with your actual file locations from your Hyperledger Fabric deployment. -- GitLab From 2910da0518c2978b67b4af8843641a8e9d35e1c1 Mon Sep 17 00:00:00 2001 From: jjdiaz Date: Mon, 16 Sep 2024 14:52:48 +0200 Subject: [PATCH 86/94] Code Cleanup --- .../service/topology_abstractor/DltRecordSender.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index 5605ae412..f91d6d547 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -45,27 +45,27 @@ class DltRecordSender: def add_device(self, topology_id: TopologyId, device: Device) -> None: topology_uuid = topology_id.topology_uuid.uuid device_uuid = device.device_id.device_uuid.uuid - record_uuid = f'{topology_uuid}:device:{device_uuid}' + record_uuid = '{:s}:device:{:s}'.format(topology_uuid, device_uuid) self._add_record(record_uuid, (topology_id, device)) def add_link(self, topology_id: TopologyId, link: Link) -> None: topology_uuid = topology_id.topology_uuid.uuid link_uuid = link.link_id.link_uuid.uuid - record_uuid = f'{topology_uuid}:link:{link_uuid}' + record_uuid = '{:s}:link:{:s}'.format(topology_uuid, link_uuid) self._add_record(record_uuid, (topology_id, link)) def add_service(self, topology_id: TopologyId, service: Service) -> None: topology_uuid = topology_id.topology_uuid.uuid context_uuid = service.service_id.context_id.context_uuid.uuid service_uuid = service.service_id.service_uuid.uuid - record_uuid = f'{topology_uuid}:service:{context_uuid}/{service_uuid}' + record_uuid = '{:s}:service:{:s}/{:s}'.format(topology_uuid, context_uuid, service_uuid) self._add_record(record_uuid, (topology_id, service)) def add_slice(self, topology_id: TopologyId, slice_: Slice) -> None: topology_uuid = topology_id.topology_uuid.uuid context_uuid = slice_.slice_id.context_id.context_uuid.uuid slice_uuid = slice_.slice_id.slice_uuid.uuid - record_uuid = f'{topology_uuid}:slice:{context_uuid}/{slice_uuid}' + record_uuid = '{:s}:slice:{:s}/{:s}'.format(topology_uuid, context_uuid, slice_uuid) self._add_record(record_uuid, (topology_id, slice_)) async def commit(self) -> None: @@ -83,7 +83,6 @@ class DltRecordSender: dlt_device_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_device_id.device_id.CopyFrom(device_id) # pylint: disable=no-member tasks.append(self.dlt_connector_client.RecordDevice(dlt_device_id)) -# await self.dlt_connector_client.RecordDevice(dlt_device_id) elif isinstance(dlt_record, Link): link_id = dlt_record.link_id if self.dlt_connector_client is None: continue @@ -91,7 +90,6 @@ class DltRecordSender: dlt_link_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_link_id.link_id.CopyFrom(link_id) # pylint: disable=no-member tasks.append(self.dlt_connector_client.RecordLink(dlt_link_id)) - #await self.dlt_connector_client.RecordLink(dlt_link_id) elif isinstance(dlt_record, Service): service_id = dlt_record.service_id if self.dlt_connector_client is None: continue @@ -99,7 +97,6 @@ class DltRecordSender: dlt_service_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_service_id.service_id.CopyFrom(service_id) # pylint: disable=no-member tasks.append(self.dlt_connector_client.RecordService(dlt_service_id)) - #await self.dlt_connector_client.RecordService(dlt_service_id) elif isinstance(dlt_record, Slice): slice_id = dlt_record.slice_id if self.dlt_connector_client is None: continue @@ -107,7 +104,6 @@ class DltRecordSender: dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member tasks.append(self.dlt_connector_client.RecordSlice(dlt_slice_id)) - #await self.dlt_connector_client.RecordSlice(dlt_slice_id) else: LOGGER.error(f'Unsupported Record({str(dlt_record)})') -- GitLab From f62d28c746bf3cde9511f2f2d6e358820ec3c72e Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 18 Sep 2024 16:26:37 +0000 Subject: [PATCH 87/94] Pre-merge code cleanup --- deploy/tfs.sh | 114 ++++++++---------- manifests/dltservice.yaml | 12 ++ .../connector/client/DltConnectorClient.py | 1 - .../client/DltConnectorClientAsync.py | 1 - .../connector/client/DltEventsCollector.py | 1 - src/dlt/connector/client/DltGatewayClient.py | 1 - .../connector/client/DltGatewayClientAsync.py | 1 - .../DltConnectorServiceServicerImpl.py | 1 - .../topology_abstractor/DltRecordSender.py | 1 - .../topology_abstractor/DltRecorder.py | 1 - 10 files changed, 65 insertions(+), 69 deletions(-) diff --git a/deploy/tfs.sh b/deploy/tfs.sh index 293a56f0f..32605143f 100755 --- a/deploy/tfs.sh +++ b/deploy/tfs.sh @@ -45,10 +45,6 @@ export TFS_GRAFANA_PASSWORD=${TFS_GRAFANA_PASSWORD:-"admin123+"} # If TFS_SKIP_BUILD is "YES", the containers are not rebuilt-retagged-repushed and existing ones are used. export TFS_SKIP_BUILD=${TFS_SKIP_BUILD:-""} -# If not already set, disable build-if-exists flag to skip building Docker images if they already exist. -# If TFS_BUILD_IF_EXISTS is "NO", the containers are not rebuilt if they already exist. -export TFS_BUILD_IF_EXISTS=${TFS_BUILD_IF_EXISTS:-"YES"} - # ----- CockroachDB ------------------------------------------------------------ @@ -118,6 +114,7 @@ export PROM_EXT_PORT_HTTP=${PROM_EXT_PORT_HTTP:-"9090"} # If not already set, set the external port Grafana HTTP Dashboards will be exposed to. export GRAF_EXT_PORT_HTTP=${GRAF_EXT_PORT_HTTP:-"3000"} + ######################################################################################################################## # Automated steps start here ######################################################################################################################## @@ -247,77 +244,72 @@ for COMPONENT in $TFS_COMPONENTS; do echo "Processing '$COMPONENT' component..." if [ "$TFS_SKIP_BUILD" != "YES" ]; then - IMAGE_EXISTS=$(docker images -q "$COMPONENT:$TFS_IMAGE_TAG") - if [ -z "$IMAGE_EXISTS" ] || [ "$TFS_BUILD_IF_EXISTS" == "YES" ]; then - echo " Building Docker image..." - BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}.log" - - if [ "$COMPONENT" == "ztp" ] || [ "$COMPONENT" == "policy" ]; then - $DOCKER_BUILD -t "$COMPONENT:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/Dockerfile ./src/"$COMPONENT"/ > "$BUILD_LOG" - elif [ "$COMPONENT" == "pathcomp" ]; then - BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-frontend.log" - $DOCKER_BUILD -t "$COMPONENT-frontend:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/frontend/Dockerfile . > "$BUILD_LOG" - - BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-backend.log" - $DOCKER_BUILD -t "$COMPONENT-backend:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/backend/Dockerfile . > "$BUILD_LOG" - # next command is redundant, but helpful to keep cache updated between rebuilds - IMAGE_NAME="$COMPONENT-backend:$TFS_IMAGE_TAG-builder" - $DOCKER_BUILD -t "$IMAGE_NAME" --target builder -f ./src/"$COMPONENT"/backend/Dockerfile . >> "$BUILD_LOG" - elif [ "$COMPONENT" == "dlt" ]; then - BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-connector.log" - $DOCKER_BUILD -t "$COMPONENT-connector:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/connector/Dockerfile . > "$BUILD_LOG" - - BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-gateway.log" - $DOCKER_BUILD -t "$COMPONENT-gateway:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/gateway/Dockerfile . > "$BUILD_LOG" - else - $DOCKER_BUILD -t "$COMPONENT:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/Dockerfile . > "$BUILD_LOG" - fi + echo " Building Docker image..." + BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}.log" + + if [ "$COMPONENT" == "ztp" ] || [ "$COMPONENT" == "policy" ]; then + $DOCKER_BUILD -t "$COMPONENT:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/Dockerfile ./src/"$COMPONENT"/ > "$BUILD_LOG" + elif [ "$COMPONENT" == "pathcomp" ]; then + BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-frontend.log" + $DOCKER_BUILD -t "$COMPONENT-frontend:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/frontend/Dockerfile . > "$BUILD_LOG" + + BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-backend.log" + $DOCKER_BUILD -t "$COMPONENT-backend:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/backend/Dockerfile . > "$BUILD_LOG" + # next command is redundant, but helpful to keep cache updated between rebuilds + IMAGE_NAME="$COMPONENT-backend:$TFS_IMAGE_TAG-builder" + $DOCKER_BUILD -t "$IMAGE_NAME" --target builder -f ./src/"$COMPONENT"/backend/Dockerfile . >> "$BUILD_LOG" + elif [ "$COMPONENT" == "dlt" ]; then + BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-connector.log" + $DOCKER_BUILD -t "$COMPONENT-connector:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/connector/Dockerfile . > "$BUILD_LOG" + + BUILD_LOG="$TMP_LOGS_FOLDER/build_${COMPONENT}-gateway.log" + $DOCKER_BUILD -t "$COMPONENT-gateway:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/gateway/Dockerfile . > "$BUILD_LOG" + else + $DOCKER_BUILD -t "$COMPONENT:$TFS_IMAGE_TAG" -f ./src/"$COMPONENT"/Dockerfile . > "$BUILD_LOG" + fi - echo " Pushing Docker image to '$TFS_REGISTRY_IMAGES'..." + echo " Pushing Docker image to '$TFS_REGISTRY_IMAGES'..." - if [ "$COMPONENT" == "pathcomp" ]; then - IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-frontend:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') + if [ "$COMPONENT" == "pathcomp" ]; then + IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-frontend:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') - TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-frontend.log" - docker tag "$COMPONENT-frontend:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" + TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-frontend.log" + docker tag "$COMPONENT-frontend:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" - PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-frontend.log" - docker push "$IMAGE_URL" > "$PUSH_LOG" + PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-frontend.log" + docker push "$IMAGE_URL" > "$PUSH_LOG" - IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-backend:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') + IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-backend:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') - TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-backend.log" - docker tag "$COMPONENT-backend:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" + TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-backend.log" + docker tag "$COMPONENT-backend:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" - PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-backend.log" - docker push "$IMAGE_URL" > "$PUSH_LOG" - elif [ "$COMPONENT" == "dlt" ]; then - IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-connector:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') + PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-backend.log" + docker push "$IMAGE_URL" > "$PUSH_LOG" + elif [ "$COMPONENT" == "dlt" ]; then + IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-connector:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') - TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-connector.log" - docker tag "$COMPONENT-connector:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" + TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-connector.log" + docker tag "$COMPONENT-connector:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" - PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-connector.log" - docker push "$IMAGE_URL" > "$PUSH_LOG" + PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-connector.log" + docker push "$IMAGE_URL" > "$PUSH_LOG" - IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-gateway:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') + IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT-gateway:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') - TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-gateway.log" - docker tag "$COMPONENT-gateway:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" + TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}-gateway.log" + docker tag "$COMPONENT-gateway:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" - PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-gateway.log" - docker push "$IMAGE_URL" > "$PUSH_LOG" - else - IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') + PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}-gateway.log" + docker push "$IMAGE_URL" > "$PUSH_LOG" + else + IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') - TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}.log" - docker tag "$COMPONENT:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" + TAG_LOG="$TMP_LOGS_FOLDER/tag_${COMPONENT}.log" + docker tag "$COMPONENT:$TFS_IMAGE_TAG" "$IMAGE_URL" > "$TAG_LOG" - PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}.log" - docker push "$IMAGE_URL" > "$PUSH_LOG" - fi - else - echo " Skipping Docker build for '$COMPONENT' as the image already exists and TFS_BUILD_IF_EXISTS is set to 'NO'." + PUSH_LOG="$TMP_LOGS_FOLDER/push_${COMPONENT}.log" + docker push "$IMAGE_URL" > "$PUSH_LOG" fi fi diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index 35ab9919d..c79158801 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -74,6 +74,18 @@ spec: imagePullPolicy: Always ports: - containerPort: 50051 + #readinessProbe: + # httpGet: + # path: /health + # port: 8081 + # initialDelaySeconds: 5 + # timeoutSeconds: 5 + #livenessProbe: + # httpGet: + # path: /health + # port: 8081 + # initialDelaySeconds: 5 + # timeoutSeconds: 5 resources: requests: cpu: 200m diff --git a/src/dlt/connector/client/DltConnectorClient.py b/src/dlt/connector/client/DltConnectorClient.py index e383217d8..c3101d65e 100644 --- a/src/dlt/connector/client/DltConnectorClient.py +++ b/src/dlt/connector/client/DltConnectorClient.py @@ -22,7 +22,6 @@ from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.grpc.Tools import grpc_message_to_json_string LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) MAX_RETRIES = 15 DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') diff --git a/src/dlt/connector/client/DltConnectorClientAsync.py b/src/dlt/connector/client/DltConnectorClientAsync.py index 23cd63fa0..b9ed8a4d6 100644 --- a/src/dlt/connector/client/DltConnectorClientAsync.py +++ b/src/dlt/connector/client/DltConnectorClientAsync.py @@ -23,7 +23,6 @@ from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.grpc.Tools import grpc_message_to_json_string LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) MAX_RETRIES = 15 DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') diff --git a/src/dlt/connector/client/DltEventsCollector.py b/src/dlt/connector/client/DltEventsCollector.py index e59784a4d..ce7d01480 100644 --- a/src/dlt/connector/client/DltEventsCollector.py +++ b/src/dlt/connector/client/DltEventsCollector.py @@ -19,7 +19,6 @@ from common.tools.grpc.Tools import grpc_message_to_json_string from dlt.connector.client.DltGatewayClient import DltGatewayClient LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) # This class accepts an event_handler method as attribute that can be used to pre-process and # filter events before they reach the events_queue. Depending on the handler, the supported diff --git a/src/dlt/connector/client/DltGatewayClient.py b/src/dlt/connector/client/DltGatewayClient.py index 71f336866..5e0328380 100644 --- a/src/dlt/connector/client/DltGatewayClient.py +++ b/src/dlt/connector/client/DltGatewayClient.py @@ -22,7 +22,6 @@ from common.tools.grpc.Tools import grpc_message_to_json_string from dlt.connector.Config import DLT_GATEWAY_HOST, DLT_GATEWAY_PORT LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) MAX_RETRIES = 15 DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') diff --git a/src/dlt/connector/client/DltGatewayClientAsync.py b/src/dlt/connector/client/DltGatewayClientAsync.py index 84c753e03..3f1cf5396 100644 --- a/src/dlt/connector/client/DltGatewayClientAsync.py +++ b/src/dlt/connector/client/DltGatewayClientAsync.py @@ -23,7 +23,6 @@ from common.tools.grpc.Tools import grpc_message_to_json_string from dlt.connector.Config import DLT_GATEWAY_HOST, DLT_GATEWAY_PORT LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) MAX_RETRIES = 15 DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') diff --git a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py index edba01fac..2222f5a29 100644 --- a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py +++ b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py @@ -26,7 +26,6 @@ from dlt.connector.client.DltGatewayClientAsync import DltGatewayClientAsync from .tools.Checkers import record_exists LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) METRICS_POOL = MetricsPool('DltConnector', 'RPC') diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index f91d6d547..ae9fd440b 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -23,7 +23,6 @@ from context.client.ContextClient import ContextClient from dlt.connector.client.DltConnectorClientAsync import DltConnectorClientAsync LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) class DltRecordSender: def __init__(self, context_client: ContextClient) -> None: diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index 79067072c..ae869b7c0 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -26,7 +26,6 @@ from .DltRecordSender import DltRecordSender from .Types import EventTypes LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) INTERDOMAIN_TOPOLOGY_ID = TopologyId(**json_topology_id(INTERDOMAIN_TOPOLOGY_NAME, context_id=ADMIN_CONTEXT_ID)) -- GitLab From c43486a27146595744f2f144adc1d54dcdfc153e Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 18 Sep 2024 16:31:47 +0000 Subject: [PATCH 88/94] Pre-merge code cleanup --- manifests/dltservice.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index c79158801..66bd3724c 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -17,11 +17,11 @@ kind: ConfigMap metadata: name: dlt-config data: - CHANNEL_NAME: "channel1" #Change according to your blockchain configuration - CHAINCODE_NAME: "adrenalineDLT" #Change according to your blockchain configuration - MSP_ID: "Org1MSP" #Change according to your blockchain configuration - PEER_ENDPOINT: "10.1.1.96:7051" #Change to required peer address according to your blockchain deployment# - PEER_HOST_ALIAS: "peer0.org1.adrenaline.com" #Change according to your blockchain configuration + CHANNEL_NAME: "tfs_channel" # Change according to your blockchain configuration + CHAINCODE_NAME: "tfs_dlt" # Change according to your blockchain configuration + MSP_ID: "ETSI" # Change according to your blockchain configuration + PEER_ENDPOINT: "127.0.0.1:7051" # Change according to your blockchain configuration + PEER_HOST_ALIAS: "peer0.org1.tfs.etsi.org" # Change according to your blockchain configuration KEY_DIRECTORY_PATH: "/etc/hyperledger/fabric-keystore/keystore" CERT_DIRECTORY_PATH: "/etc/hyperledger/fabric-signcerts/signcerts.pem" TLS_CERT_PATH: "/etc/hyperledger/fabric-ca-crt/ca.crt" -- GitLab From 614c625f0d0beb6000b5d026a966cd447744f65c Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 18 Sep 2024 16:37:20 +0000 Subject: [PATCH 89/94] Pre-merge code cleanup --- src/dlt/connector/client/DltGatewayClient.py | 33 ++++++++++++++++++- .../connector/client/DltGatewayClientAsync.py | 9 +++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/dlt/connector/client/DltGatewayClient.py b/src/dlt/connector/client/DltGatewayClient.py index 5e0328380..8e2303b2c 100644 --- a/src/dlt/connector/client/DltGatewayClient.py +++ b/src/dlt/connector/client/DltGatewayClient.py @@ -15,7 +15,10 @@ import grpc, logging from typing import Iterator -from common.proto.dlt_gateway_pb2 import DltRecordEvent, DltRecordSubscription +from common.proto.context_pb2 import Empty, TeraFlowController +from common.proto.dlt_gateway_pb2 import ( + DltPeerStatus, DltPeerStatusList, DltRecord, DltRecordEvent, DltRecordId, DltRecordStatus, DltRecordSubscription +) from common.proto.dlt_gateway_pb2_grpc import DltGatewayServiceStub from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.grpc.Tools import grpc_message_to_json_string @@ -47,9 +50,37 @@ class DltGatewayClient: self.channel = None self.stub = None + @RETRY_DECORATOR + def RecordToDlt(self, request : DltRecord) -> DltRecordStatus: + LOGGER.debug('RecordToDlt request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.RecordToDlt(request) + LOGGER.debug('RecordToDlt result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def GetFromDlt(self, request : DltRecordId) -> DltRecord: + LOGGER.debug('GetFromDlt request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.GetFromDlt(request) + LOGGER.debug('GetFromDlt result: {:s}'.format(grpc_message_to_json_string(response))) + return response + @RETRY_DECORATOR def SubscribeToDlt(self, request: DltRecordSubscription) -> Iterator[DltRecordEvent]: LOGGER.debug('SubscribeToDlt request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.SubscribeToDlt(request) LOGGER.debug('SubscribeToDlt result: {:s}'.format(grpc_message_to_json_string(response))) return response + + @RETRY_DECORATOR + def GetDltStatus(self, request : TeraFlowController) -> DltPeerStatus: + LOGGER.debug('GetDltStatus request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.GetDltStatus(request) + LOGGER.debug('GetDltStatus result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def GetDltPeers(self, request : Empty) -> DltPeerStatusList: + LOGGER.debug('GetDltPeers request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.GetDltPeers(request) + LOGGER.debug('GetDltPeers result: {:s}'.format(grpc_message_to_json_string(response))) + return response diff --git a/src/dlt/connector/client/DltGatewayClientAsync.py b/src/dlt/connector/client/DltGatewayClientAsync.py index 3f1cf5396..816241ec5 100644 --- a/src/dlt/connector/client/DltGatewayClientAsync.py +++ b/src/dlt/connector/client/DltGatewayClientAsync.py @@ -12,11 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc, logging, asyncio +import asyncio, grpc, logging from typing import Iterator, List from common.proto.context_pb2 import Empty, TeraFlowController from common.proto.dlt_gateway_pb2 import ( - DltPeerStatus, DltPeerStatusList, DltRecord, DltRecordEvent, DltRecordId, DltRecordStatus, DltRecordSubscription) + DltPeerStatus, DltPeerStatusList, DltRecord, DltRecordEvent, DltRecordId, DltRecordStatus, DltRecordSubscription +) from common.proto.dlt_gateway_pb2_grpc import DltGatewayServiceStub from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.grpc.Tools import grpc_message_to_json_string @@ -35,9 +36,7 @@ class DltGatewayClientAsync: LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) self.channel = None self.stub = None - #self.connect() self.message_queue: List[DltRecord] = [] - #LOGGER.debug('Channel created') async def connect(self): self.channel = grpc.aio.insecure_channel(self.endpoint) @@ -56,7 +55,7 @@ class DltGatewayClientAsync: response = await self.stub.RecordToDlt(request) LOGGER.debug('RecordToDlt result: {:s}'.format(grpc_message_to_json_string(response))) return response - + @RETRY_DECORATOR async def GetFromDlt(self, request : DltRecordId) -> DltRecord: LOGGER.debug('GetFromDlt request: {:s}'.format(grpc_message_to_json_string(request))) -- GitLab From 3ffe8ac8d90a277e82321c14cc169d1f2a8ef835 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 18 Sep 2024 16:41:25 +0000 Subject: [PATCH 90/94] Pre-merge code cleanup --- src/dlt/connector/client/DltGatewayClient.py | 2 +- .../DltConnectorServiceServicerImpl.py | 22 +++++++++---------- src/dlt/connector/service/__main__.py | 5 +++-- src/dlt/connector/service/tools/Checkers.py | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/dlt/connector/client/DltGatewayClient.py b/src/dlt/connector/client/DltGatewayClient.py index 8e2303b2c..21d4df57d 100644 --- a/src/dlt/connector/client/DltGatewayClient.py +++ b/src/dlt/connector/client/DltGatewayClient.py @@ -65,7 +65,7 @@ class DltGatewayClient: return response @RETRY_DECORATOR - def SubscribeToDlt(self, request: DltRecordSubscription) -> Iterator[DltRecordEvent]: + def SubscribeToDlt(self, request : DltRecordSubscription) -> Iterator[DltRecordEvent]: LOGGER.debug('SubscribeToDlt request: {:s}'.format(grpc_message_to_json_string(request))) response = self.stub.SubscribeToDlt(request) LOGGER.debug('SubscribeToDlt result: {:s}'.format(grpc_message_to_json_string(response))) diff --git a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py index 2222f5a29..1885cc153 100644 --- a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py +++ b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py @@ -39,15 +39,15 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): await self.dltgateway_client.connect() @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) - async def RecordAll(self, request: TopologyId, context: ServicerContext) -> Empty: + async def RecordAll(self, request : TopologyId, context : ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) - async def RecordAllDevices(self, request: TopologyId, context: ServicerContext) -> Empty: + async def RecordAllDevices(self, request : TopologyId, context : ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) - async def RecordDevice(self, request: DltDeviceId, context: ServicerContext) -> Empty: + async def RecordDevice(self, request : DltDeviceId, context : ServicerContext) -> Empty: data_json = None LOGGER.debug('RECORD_DEVICE = {:s}'.format(grpc_message_to_json_string(request))) if not request.delete: @@ -61,11 +61,11 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): return Empty() @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) - async def RecordAllLinks(self, request: TopologyId, context: ServicerContext) -> Empty: + async def RecordAllLinks(self, request : TopologyId, context : ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) - async def RecordLink(self, request: DltLinkId, context: ServicerContext) -> Empty: + async def RecordLink(self, request : DltLinkId, context : ServicerContext) -> Empty: data_json = None LOGGER.debug('RECORD_LINK = {:s}'.format(grpc_message_to_json_string(request))) @@ -80,11 +80,11 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): return Empty() @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) - async def RecordAllServices(self, request: TopologyId, context: ServicerContext) -> Empty: + async def RecordAllServices(self, request : TopologyId, context : ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) - async def RecordService(self, request: DltServiceId, context: ServicerContext) -> Empty: + async def RecordService(self, request : DltServiceId, context : ServicerContext) -> Empty: data_json = None if not request.delete: context_client = ContextClient() @@ -97,11 +97,11 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): return Empty() @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) - async def RecordAllSlices(self, request: TopologyId, context: ServicerContext) -> Empty: + async def RecordAllSlices(self, request : TopologyId, context : ServicerContext) -> Empty: return Empty() @safe_and_metered_rpc_method_async(METRICS_POOL, LOGGER) - async def RecordSlice(self, request: DltSliceId, context: ServicerContext) -> Empty: + async def RecordSlice(self, request : DltSliceId, context : ServicerContext) -> Empty: data_json = None if not request.delete: context_client = ContextClient() @@ -114,8 +114,8 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): return Empty() async def _record_entity( - self, dlt_domain_uuid: str, dlt_record_type: DltRecordTypeEnum, dlt_record_uuid: str, delete: bool, - data_json: Optional[str] = None + self, dlt_domain_uuid : str, dlt_record_type : DltRecordTypeEnum, dlt_record_uuid : str, delete : bool, + data_json : Optional[str] = None ) -> None: dlt_record_id = DltRecordId() dlt_record_id.domain_uuid.uuid = dlt_domain_uuid # pylint: disable=no-member diff --git a/src/dlt/connector/service/__main__.py b/src/dlt/connector/service/__main__.py index e368f6302..632b2f781 100644 --- a/src/dlt/connector/service/__main__.py +++ b/src/dlt/connector/service/__main__.py @@ -14,6 +14,7 @@ import logging, signal, threading, asyncio +from typing import Optional from prometheus_client import start_http_server from common.Constants import ServiceNameEnum from common.Settings import ( @@ -23,7 +24,7 @@ from .event_dispatcher.DltEventDispatcher import DltEventDispatcher from .DltConnectorService import DltConnectorService terminate = threading.Event() -LOGGER: logging.Logger = None +LOGGER : Optional[logging.Logger] = None def signal_handler(signal, frame): # pylint: disable=redefined-outer-name LOGGER.warning('Terminate signal received') @@ -37,7 +38,7 @@ async def main(): LOGGER = logging.getLogger(__name__) wait_for_environment_variables([ - get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_HOST), + get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_HOST ), get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), ]) diff --git a/src/dlt/connector/service/tools/Checkers.py b/src/dlt/connector/service/tools/Checkers.py index 9afb0da07..5b19afcd2 100644 --- a/src/dlt/connector/service/tools/Checkers.py +++ b/src/dlt/connector/service/tools/Checkers.py @@ -20,5 +20,5 @@ def record_exists(record : DltRecord) -> bool: exists = exists and (record.record_id.type != DLTRECORDTYPE_UNDEFINED) exists = exists and (len(record.record_id.record_uuid.uuid) > 0) #exists = exists and (record.operation != DLTRECORDOPERATION_UNDEFINED) - #exists = exists and (len(record.data_json) > 0) #It conflicts as sometimes records do not have a data_json. + #exists = exists and (len(record.data_json) > 0) # It conflicts as sometimes records do not have a data_json. return exists -- GitLab From 57d837b95cacd197ddf418eb161785c60e0b6428 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 18 Sep 2024 16:47:24 +0000 Subject: [PATCH 91/94] Pre-merge code cleanup --- .../service/DltConnectorServiceServicerImpl.py | 8 ++++---- src/dlt/connector/service/__main__.py | 2 +- src/dlt/gateway/{legacy => _legacy}/.gitignore | 0 src/dlt/gateway/{legacy => _legacy}/Dockerfile | 0 src/dlt/gateway/{legacy => _legacy}/README.md | 0 .../gateway/{legacy => _legacy}/build.gradle.kts | 0 .../config/ca.org1.example.com-cert.pem | 0 .../{legacy => _legacy}/config/connection-org1.json | 0 .../gateway/{legacy => _legacy}/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 src/dlt/gateway/{legacy => _legacy}/gradlew | 0 src/dlt/gateway/{legacy => _legacy}/gradlew.bat | 0 .../gateway/{legacy => _legacy}/settings.gradle.kts | 0 .../{legacy => _legacy}/src/main/kotlin/Main.kt | 0 .../src/main/kotlin/fabric/ConnectGateway.kt | 0 .../src/main/kotlin/fabric/EnrollAdmin.kt | 0 .../src/main/kotlin/fabric/FabricConnector.kt | 0 .../src/main/kotlin/fabric/RegisterUser.kt | 0 .../src/main/kotlin/grpc/FabricServer.kt | 0 .../src/main/kotlin/grpc/GrpcHandler.kt | 0 .../src/main/kotlin/proto/Config.proto | 0 src/dlt/gateway/dltApp/.gitignore | 2 -- 23 files changed, 5 insertions(+), 7 deletions(-) rename src/dlt/gateway/{legacy => _legacy}/.gitignore (100%) rename src/dlt/gateway/{legacy => _legacy}/Dockerfile (100%) rename src/dlt/gateway/{legacy => _legacy}/README.md (100%) rename src/dlt/gateway/{legacy => _legacy}/build.gradle.kts (100%) rename src/dlt/gateway/{legacy => _legacy}/config/ca.org1.example.com-cert.pem (100%) rename src/dlt/gateway/{legacy => _legacy}/config/connection-org1.json (100%) rename src/dlt/gateway/{legacy => _legacy}/gradle.properties (100%) rename src/dlt/gateway/{legacy => _legacy}/gradle/wrapper/gradle-wrapper.jar (100%) rename src/dlt/gateway/{legacy => _legacy}/gradle/wrapper/gradle-wrapper.properties (100%) rename src/dlt/gateway/{legacy => _legacy}/gradlew (100%) rename src/dlt/gateway/{legacy => _legacy}/gradlew.bat (100%) rename src/dlt/gateway/{legacy => _legacy}/settings.gradle.kts (100%) rename src/dlt/gateway/{legacy => _legacy}/src/main/kotlin/Main.kt (100%) rename src/dlt/gateway/{legacy => _legacy}/src/main/kotlin/fabric/ConnectGateway.kt (100%) rename src/dlt/gateway/{legacy => _legacy}/src/main/kotlin/fabric/EnrollAdmin.kt (100%) rename src/dlt/gateway/{legacy => _legacy}/src/main/kotlin/fabric/FabricConnector.kt (100%) rename src/dlt/gateway/{legacy => _legacy}/src/main/kotlin/fabric/RegisterUser.kt (100%) rename src/dlt/gateway/{legacy => _legacy}/src/main/kotlin/grpc/FabricServer.kt (100%) rename src/dlt/gateway/{legacy => _legacy}/src/main/kotlin/grpc/GrpcHandler.kt (100%) rename src/dlt/gateway/{legacy => _legacy}/src/main/kotlin/proto/Config.proto (100%) delete mode 100644 src/dlt/gateway/dltApp/.gitignore diff --git a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py index 1885cc153..c1a4b7b1e 100644 --- a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py +++ b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py @@ -118,9 +118,9 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): data_json : Optional[str] = None ) -> None: dlt_record_id = DltRecordId() - dlt_record_id.domain_uuid.uuid = dlt_domain_uuid # pylint: disable=no-member - dlt_record_id.type = dlt_record_type - dlt_record_id.record_uuid.uuid = dlt_record_uuid # pylint: disable=no-member + dlt_record_id.domain_uuid.uuid = dlt_domain_uuid # pylint: disable=no-member + dlt_record_id.type = dlt_record_type + dlt_record_id.record_uuid.uuid = dlt_record_uuid # pylint: disable=no-member str_dlt_record_id = grpc_message_to_json_string(dlt_record_id) LOGGER.debug('[_record_entity] sent dlt_record_id = {:s}'.format(str_dlt_record_id)) @@ -132,7 +132,7 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): LOGGER.debug('[_record_entity] exists = {:s}'.format(str(exists))) dlt_record = DltRecord() - dlt_record.record_id.CopyFrom(dlt_record_id) # pylint: disable=no-member + dlt_record.record_id.CopyFrom(dlt_record_id) # pylint: disable=no-member if delete and exists: dlt_record.operation = DltRecordOperationEnum.DLTRECORDOPERATION_DELETE elif not delete and exists: diff --git a/src/dlt/connector/service/__main__.py b/src/dlt/connector/service/__main__.py index 632b2f781..d1c946124 100644 --- a/src/dlt/connector/service/__main__.py +++ b/src/dlt/connector/service/__main__.py @@ -42,7 +42,7 @@ async def main(): get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), ]) - signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) LOGGER.info('Starting...') diff --git a/src/dlt/gateway/legacy/.gitignore b/src/dlt/gateway/_legacy/.gitignore similarity index 100% rename from src/dlt/gateway/legacy/.gitignore rename to src/dlt/gateway/_legacy/.gitignore diff --git a/src/dlt/gateway/legacy/Dockerfile b/src/dlt/gateway/_legacy/Dockerfile similarity index 100% rename from src/dlt/gateway/legacy/Dockerfile rename to src/dlt/gateway/_legacy/Dockerfile diff --git a/src/dlt/gateway/legacy/README.md b/src/dlt/gateway/_legacy/README.md similarity index 100% rename from src/dlt/gateway/legacy/README.md rename to src/dlt/gateway/_legacy/README.md diff --git a/src/dlt/gateway/legacy/build.gradle.kts b/src/dlt/gateway/_legacy/build.gradle.kts similarity index 100% rename from src/dlt/gateway/legacy/build.gradle.kts rename to src/dlt/gateway/_legacy/build.gradle.kts diff --git a/src/dlt/gateway/legacy/config/ca.org1.example.com-cert.pem b/src/dlt/gateway/_legacy/config/ca.org1.example.com-cert.pem similarity index 100% rename from src/dlt/gateway/legacy/config/ca.org1.example.com-cert.pem rename to src/dlt/gateway/_legacy/config/ca.org1.example.com-cert.pem diff --git a/src/dlt/gateway/legacy/config/connection-org1.json b/src/dlt/gateway/_legacy/config/connection-org1.json similarity index 100% rename from src/dlt/gateway/legacy/config/connection-org1.json rename to src/dlt/gateway/_legacy/config/connection-org1.json diff --git a/src/dlt/gateway/legacy/gradle.properties b/src/dlt/gateway/_legacy/gradle.properties similarity index 100% rename from src/dlt/gateway/legacy/gradle.properties rename to src/dlt/gateway/_legacy/gradle.properties diff --git a/src/dlt/gateway/legacy/gradle/wrapper/gradle-wrapper.jar b/src/dlt/gateway/_legacy/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from src/dlt/gateway/legacy/gradle/wrapper/gradle-wrapper.jar rename to src/dlt/gateway/_legacy/gradle/wrapper/gradle-wrapper.jar diff --git a/src/dlt/gateway/legacy/gradle/wrapper/gradle-wrapper.properties b/src/dlt/gateway/_legacy/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from src/dlt/gateway/legacy/gradle/wrapper/gradle-wrapper.properties rename to src/dlt/gateway/_legacy/gradle/wrapper/gradle-wrapper.properties diff --git a/src/dlt/gateway/legacy/gradlew b/src/dlt/gateway/_legacy/gradlew similarity index 100% rename from src/dlt/gateway/legacy/gradlew rename to src/dlt/gateway/_legacy/gradlew diff --git a/src/dlt/gateway/legacy/gradlew.bat b/src/dlt/gateway/_legacy/gradlew.bat similarity index 100% rename from src/dlt/gateway/legacy/gradlew.bat rename to src/dlt/gateway/_legacy/gradlew.bat diff --git a/src/dlt/gateway/legacy/settings.gradle.kts b/src/dlt/gateway/_legacy/settings.gradle.kts similarity index 100% rename from src/dlt/gateway/legacy/settings.gradle.kts rename to src/dlt/gateway/_legacy/settings.gradle.kts diff --git a/src/dlt/gateway/legacy/src/main/kotlin/Main.kt b/src/dlt/gateway/_legacy/src/main/kotlin/Main.kt similarity index 100% rename from src/dlt/gateway/legacy/src/main/kotlin/Main.kt rename to src/dlt/gateway/_legacy/src/main/kotlin/Main.kt diff --git a/src/dlt/gateway/legacy/src/main/kotlin/fabric/ConnectGateway.kt b/src/dlt/gateway/_legacy/src/main/kotlin/fabric/ConnectGateway.kt similarity index 100% rename from src/dlt/gateway/legacy/src/main/kotlin/fabric/ConnectGateway.kt rename to src/dlt/gateway/_legacy/src/main/kotlin/fabric/ConnectGateway.kt diff --git a/src/dlt/gateway/legacy/src/main/kotlin/fabric/EnrollAdmin.kt b/src/dlt/gateway/_legacy/src/main/kotlin/fabric/EnrollAdmin.kt similarity index 100% rename from src/dlt/gateway/legacy/src/main/kotlin/fabric/EnrollAdmin.kt rename to src/dlt/gateway/_legacy/src/main/kotlin/fabric/EnrollAdmin.kt diff --git a/src/dlt/gateway/legacy/src/main/kotlin/fabric/FabricConnector.kt b/src/dlt/gateway/_legacy/src/main/kotlin/fabric/FabricConnector.kt similarity index 100% rename from src/dlt/gateway/legacy/src/main/kotlin/fabric/FabricConnector.kt rename to src/dlt/gateway/_legacy/src/main/kotlin/fabric/FabricConnector.kt diff --git a/src/dlt/gateway/legacy/src/main/kotlin/fabric/RegisterUser.kt b/src/dlt/gateway/_legacy/src/main/kotlin/fabric/RegisterUser.kt similarity index 100% rename from src/dlt/gateway/legacy/src/main/kotlin/fabric/RegisterUser.kt rename to src/dlt/gateway/_legacy/src/main/kotlin/fabric/RegisterUser.kt diff --git a/src/dlt/gateway/legacy/src/main/kotlin/grpc/FabricServer.kt b/src/dlt/gateway/_legacy/src/main/kotlin/grpc/FabricServer.kt similarity index 100% rename from src/dlt/gateway/legacy/src/main/kotlin/grpc/FabricServer.kt rename to src/dlt/gateway/_legacy/src/main/kotlin/grpc/FabricServer.kt diff --git a/src/dlt/gateway/legacy/src/main/kotlin/grpc/GrpcHandler.kt b/src/dlt/gateway/_legacy/src/main/kotlin/grpc/GrpcHandler.kt similarity index 100% rename from src/dlt/gateway/legacy/src/main/kotlin/grpc/GrpcHandler.kt rename to src/dlt/gateway/_legacy/src/main/kotlin/grpc/GrpcHandler.kt diff --git a/src/dlt/gateway/legacy/src/main/kotlin/proto/Config.proto b/src/dlt/gateway/_legacy/src/main/kotlin/proto/Config.proto similarity index 100% rename from src/dlt/gateway/legacy/src/main/kotlin/proto/Config.proto rename to src/dlt/gateway/_legacy/src/main/kotlin/proto/Config.proto diff --git a/src/dlt/gateway/dltApp/.gitignore b/src/dlt/gateway/dltApp/.gitignore deleted file mode 100644 index 9e21c69ae..000000000 --- a/src/dlt/gateway/dltApp/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -operation_times.txt \ No newline at end of file -- GitLab From 51765439657bab5e22a897b08f24e1dcfaa76dbe Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 18 Sep 2024 16:56:46 +0000 Subject: [PATCH 92/94] Pre-merge code cleanup --- src/dlt/gateway/Dockerfile | 3 -- src/dlt/gateway/README.md | 38 ++++++++++++++--------- src/dlt/gateway/dltApp/package.json | 2 +- src/dlt/gateway/keys/.gitignore | 2 +- src/dlt/gateway/resources/System.png | Bin 291845 -> 0 bytes src/dlt/gateway/samples/sampleTopo.json | 2 +- src/dlt/gateway/samples/updatedTopo.json | 2 +- src/dlt/gateway/tests/testEvents.js | 2 +- src/dlt/gateway/tests/testGateway.js | 2 +- 9 files changed, 29 insertions(+), 24 deletions(-) delete mode 100644 src/dlt/gateway/resources/System.png diff --git a/src/dlt/gateway/Dockerfile b/src/dlt/gateway/Dockerfile index 78e735c09..ace9cb22e 100644 --- a/src/dlt/gateway/Dockerfile +++ b/src/dlt/gateway/Dockerfile @@ -38,11 +38,8 @@ COPY src/dlt/gateway/dltApp/src/ ./src # Install dependencies RUN npm install - - # Expose the port that the gRPC service runs on EXPOSE 50051 # Command to run the service CMD ["node", "src/dltGateway.js"] -#CMD ["sh", "-c", "sleep 3600"] # Keep the container running for testing diff --git a/src/dlt/gateway/README.md b/src/dlt/gateway/README.md index ae05ed6c6..4a38545ea 100644 --- a/src/dlt/gateway/README.md +++ b/src/dlt/gateway/README.md @@ -2,9 +2,15 @@ ## Description -The DLT Gateway consists of a **fabricConnect.ts** TypeScript file, which contains the logic for identification management (certificates required for the MSP), connection management to the blockchain, and finally, it exposes a contract object with all the required information for interacting with the chaincode. The **fabricConnect.ts** is coded following the Fabric Gateway API recommendations from Hyperledger Fabric 2.4+. The compiled **fabricConnect.ts** logic is imported into a **dltGateway.js** file, which contains the gRPC logic for interaction with the TFS controller. Testing code for various performance tests is included inside the [/tests](./tests/) folder. +The DLT Gateway consists of a **fabricConnect.ts** TypeScript file, which contains the logic for identification +management (certificates required for the MSP), connection management to the blockchain, and finally, it exposes a +contract object with all the required information for interacting with the chaincode. The **fabricConnect.ts** is +coded following the Fabric Gateway API recommendations from Hyperledger Fabric 2.4+. The compiled **fabricConnect.ts** +logic is imported into a **dltGateway.js** file, which contains the gRPC logic for interaction with the TFS controller. +Testing code for various performance tests is included inside the [/tests](./tests/) folder. -The chaincode is written in Go, providing a reference for the operations that are recorded in the blockchain. This chaincode must already be deployed in a working Hyperledger Fabric blockchain. +The chaincode is written in Go, providing a reference for the operations that are recorded in the blockchain. This +chaincode must already be deployed in a working Hyperledger Fabric blockchain. ## Requisites @@ -12,16 +18,18 @@ The chaincode is written in Go, providing a reference for the operations that ar * Docker * Kubernetes (K8s) -Sign and TLS certificates, and private key of the MSP user from the Hyperledger Fabric deployment must be copied to the [/keys](./keys/) directory inside this repository. - - Example: - - ```bash - cp ~/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/tls/ca.crt src/dlt/gateway/keys/ - - cp ~/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem src/dlt/gateway/keys/cert.pem - - cp ~/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv_sk src/dlt/gateway/keys/ - ``` - -These files are essential for establishing the identity and secure connection to the blockchain. Make sure you replace the paths with your actual file locations from your Hyperledger Fabric deployment. +Sign and TLS certificates, and private key of the MSP user from the Hyperledger Fabric deployment must be copied to the +[/keys](./keys/) directory inside this repository. + +Example: + +```bash +cp ~/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/tls/ca.crt src/dlt/gateway/keys/ + +cp ~/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem src/dlt/gateway/keys/cert.pem + +cp ~/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv_sk src/dlt/gateway/keys/ +``` + +These files are essential for establishing the identity and secure connection to the blockchain. Make sure you replace +the paths with your actual file locations from your Hyperledger Fabric deployment. diff --git a/src/dlt/gateway/dltApp/package.json b/src/dlt/gateway/dltApp/package.json index 5eabc2dff..9d29b5287 100644 --- a/src/dlt/gateway/dltApp/package.json +++ b/src/dlt/gateway/dltApp/package.json @@ -16,7 +16,7 @@ "start": "node dist/dlt_gateway.js" }, "engineStrict": true, - "author": "CTTC-Javier", + "author": "Javier Jose Diaz (CTTC)", "license": "Apache-2.0", "dependencies": { "@grpc/grpc-js": "^1.10.8", diff --git a/src/dlt/gateway/keys/.gitignore b/src/dlt/gateway/keys/.gitignore index 28d89119f..b54a37fc5 100644 --- a/src/dlt/gateway/keys/.gitignore +++ b/src/dlt/gateway/keys/.gitignore @@ -1,3 +1,3 @@ ca.crt cert.pem -priv_sk \ No newline at end of file +priv_sk diff --git a/src/dlt/gateway/resources/System.png b/src/dlt/gateway/resources/System.png deleted file mode 100644 index f8c6b79f08e44842e7a486ede2477b987cbeb9d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 291845 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
0`mI47*@tD5cxnKl3TA+U)& z)6%`)d^(N!Oa5nYRY~NmeB)B3jVsYwl}Y1wFp5fT_Lg|5fQj^JxO9$@2mJ$%cAZ0a zyH9uyKX~9^URS~VMR-DF?!xQlV7*Gxgup0WFy8mM2(>ovRjWv!0!Q1q*u>#L&)^4w zJfOT5Ort*+VLa{AtF^8Fs}VEuN91^!@En#I0<6MZtLv6Lxr}#uY3NI;%ys$gvK5-S zNh8)XooH$eL52;$1pt{_?tQ@)ic0qy8v ziljjQCvCvR2#s%0{;9$Qj3vtAr2EZC5>Wj0g-0^KO5!t*t(L8YQ`kdQeGhh~{0f|q z1%LqRBrzuCiLnu4dtzN^z$On&0d`g4)%lhUnD|i}pIOP;_^i}5!#aX{A{=qemXa%Ao+=u>+_v3@oh^vHer2S$>CTsegTsscygB?3e5DwZf0t&kHhpgJ;;g?hn)d*ZR zQ9J}1Xf+XzMm4V(+zX-C`&J%|3}z?WIe5oBsAns~ufF@RzxC{*3xnMtGW*q_+aC=? z$ce#eMp8XSgb5IB)QLU<4^~_vr<;wQ8ct)L-oNt0u*n$;s#OAG?uskcHT(K2>sYKx z95tLERvapo+JzxNMJ~jW6Ng|8b3ey zmSg`<`>Bwh+sR2p43um2*KXs@3!vK=nn7H7y;aILrW#}$e|}qs>YF2b*<|1eR~$v7 z27GS#+w4*0*(OGGnUNh1MT1-<#&!06E4D`~U(M-BAn~2&^pr=o?k8kplZ_HJeZ3yg zwls}T#a`a0luVAkWzi!Nvm&_UtaS;bAwVVwAVrH*F-oQ&xBf_wC7;)L-LI4HXE@ym zQZ$*t9n+Q4@~?WE^ua}vWm#*oDNg5C3aE2jqqz$}J~nM2P}|IDduN@xG`Q+GFiY(w zL_zvAU2#S#>(Fk_?_-pIc!JVD^l*W;(C!9uATUP2&{8k_83?jbrR5 zdsacgdqWoaphqYJlcrTA-RG=xk>3KQPP8_{Ot@giw5n7RHMb6GyL|J7njLJ^h9Y!h zU<7Ub!m5+gTh6v7Dd%PgH+Kn93v*0TDmza20B`@77Dv zqP-z3ED#^<`2FB`au9AW*<%5ajg1Jq|a%NIbuc8~K6Xtu5rpmWVgA76%Ve2z( zb=8%Y##DHr;T*g;Cfyf|&SWApK&0i4>ailmX86uq?;AY)w|+gN_Ec_R*}>`ND(Mc< z){L0x(dY~><+c1$R}5DKRF>%|S41HVy+w+}mh`&rMmZOEh6d1IZqa5uss}Af!h*Sd zrp9*j%G0tk!$Jas={VPL8baCeZN*%zW*XA(5fpE~?x^kO=pJ8_n(kl|iYZPuk#37G zLwlIE-cJVt6=~b*4iizlbMHW-PfM}*e15ol?li~@XJB>9lPKxV({1hq&n(Hw)ldyk zBa4Zzbg~}q*#E!O@t^&8kA-Dr_}GN+_%=LiR_<55oemMQ^t(a(Z@zW;ud|bpY$w;K zyx)KGdSQ|-e?9W0NKrAk^_bk3)H90pd^J_j#bGF_m-ArJg>dip5yeT-2N5X_=GY4O ze&M*st2AZoeH!oOrL9K{ZkuOXG1}@#!sW3?E{MaLVHyZ_{ouR9vR><`(H6=i+sd0PYSGy+cI#7Nx zm%$k$I)+S$lLT>MM)_QVV9&Fwb&Sn7Zou5lt)FMFH`R(6gycg4RwPxRo0USit)P_x zD2Fz#MB)~_+->TAxBnjk`hA(dqe-(f!7KpB6LHSrltHW?-PIDDz%>AP;t zZ5L7Kp}-n+KlxhvUXI>&`(0VobBpNFW1RM2$mKxZ1a|gN(&R;{k&ykm6}aC?;Yh9h zO^aWeqv8$hCue?3mi0ux*UsTr=>E2%#kW`Vx`H|hwH#jo90x+wa2fscUUgT`d!fvt zgU_$O!n739((>P_t&NIshfUe+?hfs^XV?a`_a;OZ5Tih5J;@^@6LY=bi+I^}@_a|(8>IG7S znYm4*{)WcG2SFIfFwF{6z|Bk+o?j?OF|$BVMG>|Yy81Y5UH4090PCZFeE4iwExnfS zZeCnp8`;kDX#ml&Z3u7H6F>9bWKjqPJW*$s>5xZj()>wW@K(36D{Fd=C@cv zWPfY4s0&M)^k@tEqfs$a0Ffs-A_QK8u>o2_H&42!l~WM1FgY|E3{S0AM4a@3J08kq zupb6GR~>Ns+e~jH8)CfHK=FcE`aeH{4B@+&q%($`|ByyK^V~Q3@FgzF{d3^Ps?8z_ z#q;e-LxvZ2?0w%i-vr^QZUWvNdR9iNBpS>V1k&&o0)=~Zi}TkA}7^E($_cOBL}Sb ztr~~jnt&V?0UT*u#bbh$e_qAai!9bHq`0s~_3*?>`@}3`if+KcoDUr23$&2SNvB{Yt0zWL^Ih zX8b3^D2V6R9@$v2OKvOPXyszx7nP-q8SU1^SM(3}b`57xs1{2_jyTq(?un>J17#|D zK6kG++VRS>(JW{ekvIi!*6egRaz$nHq66ZH^pf+(1JPP^_O=7@JnH#iX2%q6dd^OmS$=T6?_}JQcK$K7 z-p78(`uB7bLzX+&p-0|(GYtZxj4fCRiKkrnv5lvGx%-zt!yh+tU%1}i`&{sI+7;a| zT^z1*LJa236h0lK;q!*UvyAfwFJmhh?FHApiW>=OD$o6_^BRlb&jRe?Q^YK(Hoc!3 z<4*+f)JnVaUTFw3_PLo6EB5Qx=RteE{X87oGLmKZD$&A6O?~5?C2?C^K8Nj;D`FPx z<2j~35NDdE?bT=%RgHE&le|G0zF+K+jgJyz$d|vMPHb|yWt$VrM~i_ z?N!lfe&Xz7Kh|cvSuSJaWkRoiYK_xH&ZC(IDeBxgENz`5vcHmS|N502x$kj{iTxz| ze&;84KmC`2YqP1Gl1a?0;L@KHPuEwZc)zabp^&xDkL!w1`v&lYy^_zvD!AIkXUe4u z=L^S>)45UGp9^~W81Idi-u7P3W!|enooTJyNK-X>M{hiZZ)J_8%L6y(T?r5Rw3Su) zCM`jEZU{NR`2EKR6X|>!Pjn(}dE;g1O1#A@)D+i+7w9?s-*z{5UF=3m=XdFrLo=#Ybt2^TA z%$-`ZFI%PCj|Yr6w{+PmAKGX)uDORaOk9JOk{k&U&%R4vvySU8%jn3i(o4 zT>7o_Pmbee!;FN6#GVjp+Cxg}=p}>pR5o#Y)q|shFZYkLl3KY_z@ySN;H?+MSSELZ z0C{gpX}-S{U(zW5lMaQj#ZQV=kF*VzF4DVRPTNzGvjLB zHTkyNB7vVse#)3jtlq>^w={A>|Gn)%A6I$YimpcQFKoZ-e!38yJf5C!oL@Qsf5}B{ zoYg4v9kA`T=SI=Q7Q*>Bsk^V)7QY&k&e!A?QIXdu7*G!lPKX;}>e#lsGzRKx(Dl3~ zz3(@$_8&+#j4;fm47=h5O3Em|t=j7vhHPtb zC7N-$dxCbEJj5%c(Df39zOk<+T-8JNvS>hAT7nOZd0 z4g0Z~dE4Ua9^Ns6N!GDi^!OB&eWKNI&*CFL6moir?mJFap)2nuW%(|TO78Mx?$och z*7T31-KP3-b7+J&KcuqWqxigT657%)uF)iU6yYZEQEyqT=|k|zJp%|jSIZvjd+r$B zSCnAhu6mmf@fgk~YVM%@|1qC@QSDho}r~lvfI^W>JhXai}bMZfkj16drZrxe+afn3i&AnnK zve5M6;Ff79-%iy1$;zK}F2h9DBi-o^dZfH-sv|VQd$=Iq0$j^>uPcGI6;p&M%e}U6#zSLA~G}!T0pqmX8zp*=-ez=LkEoI8(R=R(69mENB6=@T z2g8{1pS|~c&b#0L#ra*FbMbZQg3ns(SLcT3@q3GQz&5z4>O2q)*w!e47-T_dJaO*6l9Xj&KHNda$hy<_)xZ4t^<`-#rcK zC-^O&l7KF)vBpvwd!7yf9e=Gp3|V*gQFOvW?8?wy*UvR?bqwdg8dKdOMBt8C=}M3R zUy_Y3b9eYO$L9$6BK`LXF7IH9 zFJ`dfocDh|_)odH-Fx-^fJELL8o$`rom<84uyWB^U_P#zI!)PKC7_xr4X;+IzG2!z zhmZdR+=PH2j2mjMMEz!)QI#K*Rj`^_i32a>dZi`KgC~ zH_^O(C$cFCC)7e~vLu>tpgK0Pwg}We{%7h!i!4*6-|xYX)v_Y)!!obWfYHP8P?2Dy z^#z+4IeKvHB~W8xavEkcLw3O;M%4XG@WtwIys(S*{gTKkXjSoNk>}p5JN&C8kmkI{ z@8H^1s9>Qvun)s$NPPcQ2=O%vCK610gWpq#22p#Nuo8`;I#UHJHf23T31UDf0=->~ z?lv}(Px3?Bd(-qQaK4SqD{1X%RNV_I&UFayks@xfNmdzYYu%G`_o~}t_sEd*XW_W( zt-aMBmvSFd9Tqi*Nft&$V}= zzR$iG5FlA&k~cR`it5iQRRHxN^my|jNuR6^g`M2)2PS@sy^0trfWnARS%pd5?Dw5_xsgBd?GUbPX+~lCVBGp z^nUeOwH!gZ3;RHgv(!Fy1rn)6x%ORinu_rHvl5P(hWCg?z=E#)UC$iByhaY!?m?xwjW>gD;@Ve%=o4y5?;a}|47%!#P= zPoH3zvFVHPqlf#L8_rq{1nTrvbAd7DW(%f>L&sh?bc`T>K8MUJySL8v9}$j3j-OfxM zG4J`3p9t)|5)G|=AwpOu|Ah)~Mb4AH)4Hv)uwyp0>WYTL~mLI8)Z@)g)G zzraDcxYq?_`3o(diwAtXRp(0%R7B6qdqcKbLL6MN`EtMI(NE`ht#CWmPd()hE``ql zd5<}kciEZ--I)aR3_do#dM)z(g7EBfs{b}u5(dS0_0+r<_eVbc8TQm=l|ClfPu*sE zKBn$CULJ5cEO~$Rl#wp7da&&$h(W=XEs%^o#y2FUzjOCRo8MuKU5`1eP)kz$>^OnY zH83Ak3UYwi-Z}7NO;D4MDf$`e>A|c(zCulx|EsH*fu@C6CBqH+eHLhWK6ER*84t~B z<)gSuq|G6BAZNr?Y&;^vW=vs?txBPtvPtJ ziqdtfy;eJUmTT`o_;jogb}R2XugwzxOEedS)~ zJH+OK*%59zj?4FAjX3JGIUDZr4+aSn%RbRw|FtlEt+4(j(469nhSHA#@WnZ%mhk{c zbG+mgI&C1ysp!gU1A;uJfGr{`~yLc`9s}k8?*X`Rc5>=`W#z=M~-)H~A-=%L$2KlxEDw3PlcinVe&(;wsEDUZpmo|Jdkiw_YM@?XZEi++#y3 zj3@ybT+gc^8DYIy)_n}-1QK(n2j}|>eFgP|O}%?cQ(Cxq_`Yqb=S^GY(zCx?y^yre z0aPL<0lS{Xy;TmEA&{*Z&`Hc3h+RUir^==)@BU5Qx&m9qvt{}iM5zzIn*R-yZ9bxg z5mD5<430wFG^OBRPX6C8ha4zWjaJZR^FC^Z9qcLvWT8)GlHn`J&Jd6Rc_FvMh!cR09>63-g94$-GA&X9Gt<9O!}O3 z6`=;LKEyq7_CM)zkom8MavkQ{DNVeOVs^}L3vr8o%SQKM)Xx{aRU&H8lTLg7-nkw3 zLw2I9-d)=zm|cksx-P|*=dt$wt}UwfnDD&Ef+W5hGx3NUNtKb;l{Bk})oKq*q@S+m zWL)qjxV|i0{=Hc|Y=z^u;v+D*-^GR+>-nZY6h@p#77})=T<> z_=YIt!fUw1^tsmVRNYPtqz-jm3rM*j&EWd_NNR%xMS+Qo3QD#auknA46~!~~W%;%> zab>k}CC|`#W13)&y6SBvBXeaT9ghJa7a+LaK~ z0?l}eior^mFT3-udp2$l7lih4YVp>9rrU?(T1={yZp_L14P{=YL=B|@B^(2ijAKA# z4O+k?$QX-KaJTn`(55`9e0sm@Qc;h%U;aNrh~PlCuyecT)wla@E^wvtDLRc^L!@gL z{flzR7m6q7L0oTkzZhn!yHZB79YLq7@cVx8x-PD1xIAp72-P1akBdt8#SYeeahH*Z zefi`cnk4O))kDE&TV3|F0tIKUV2eZOkEyp9A%Aw0TTrf5{kh(EEyPSj&CjBdx?q87Jv(#a z)i$3mpMuxZ1cq!AQsih1B0TL+T`!Us0xs_LB%$u+vA_Oy)$O@(eQD)_IuMo!@nSgi zW9waH?W{kac7M=j$4n>qHs-AopRgn7f!dhapUEXZRIq1alOf;Uc{9ndx!qEvLgeE1JFP{W{p9G_LGeYQwl}~of%iH7}3P2D|a$Yq+HL~yh9aaukn*rB$3Es$7z{lX*kf;ah>n=Ed9h}4VNplM%5_@+{i=G(Bto4SnDnBE z4LVu&OL)d{o|)Fvzrba=q-?Q@wn7sAqFGI_Yz{`iu5a2{&Q~$xoWL};tXEn5%t_o! z#oW(b{LD2OSse_UX~mc8yihvK#h)xV(e-AQBZ(M#-PguQD(soWUIIglj=B@$8M&w} zEFRr2u0mdz;!4y5g%#>V9mF%sp#qz0(Tz0TV>s~TpWPRfR3F_p7G}l2pakGab>QrN zu+sX|s=yD7k(rbkW!TUOoqr zGYHQA)L&bKx^v`uLSJ1k&z=4p8K66I1%Y@2lQNuL{UC4V7=A^Z18h$Gz&ILbTsB$7 zIe0>bmawGPW^r~j6RcSZZf_9L#!{z0T8j7s@o$PU{L#B7hndkO57|O9@|~Fk`Vhsh zppSL=`J0a`gEj#SBc{&_f)|M64&QL+7%A_!y%t(%C5@Wm`a=<7Lew4Ue)x4c+41+e zileAs+8Bk#E_e!4a-Y`ddQz-9`$BDU$sO&ixELoGVrg2P!*)?*7IX{GoE6?X%#WChW753mr=g}0V1@O zg7%iP>94D92>_XRE?tB!Jrpka^ZZj@M&@(IML?yupt}`MAs4|VynT%iCf@ocy*G;4 zws&OBRZx-Es(stE8&D$c*ZSP8^h6AQlzZtt)(PU|&@DUtgasRzz2a{ublBK7G0hBE+RYgM znXM^P)t;5qoJO8GRgVegoy+Tty0mw*+&){a-`P3=r1+EfslQ!OOkRty$05RdcE?f) zv7aFWPis0LfkX-e&XayCR2O;l(N{Kh(4YmHHVTILj1cwy^csjJB&BS=UeXEa ztK>65aSiJhQ1Et+KTllJtuT>KtI(%EF|0|PVxJqjH9W|FSIp$U8Nusc{3al+N}u^e zZ)DyvE_+N4R%a+HetB@GmZEzyXyGYYUdJ7q5`n?5(tTO`5IoWhw{Yxv^?j3ea?O_C zZz@k}f7Meu`~4mmtfjF2<(G2p0+j2CMe2o=Jw5pC^9_h3QpDW-f;JX9^ol|oK=7Y| zMrVRo|H$Q^FzrzE5uk0I@*%Xp7Sx1bnAtDJSPhRWDQ8J9#@HW^aha*c1k8VUFV8CJ zZojc=>IX+8zJ6^6m(5O^U%7%zSx*_xFy&TB4`MZ%9~;Bo1;^Qe5t^Hx#-4lMs$Fa# zO0|lzff2}=m19Uh&gH*H^JbP|6I{%j*xi>k#27^4%hFw*H0%94n4c(l6|`0F#gwe2 zH&Hw%m1oOKS#qejHv;UWcAG5S>fZD2Fq~O909Q~&%KoPk3#_o4fmTioTIiPYd7`3bNAPDBXG-JBoU{8*`|f+G^-9THc?O&aVhMhn* zP3C`+b!t6TGe6PB;hi_>+aZL>(Lt+^9j2<;&2Gsfc43oy3Ly%MI@yWGUU)EPC8=os zpMUP>{qCPRTD2K@b}ODem z{CQ&KTT8s-^_B)#3y`q6*M7J+3Fw$zIX!(iMjxidp8|wgEpwK=4G_ey6l&nki%zE( zHDt`;`z|E%=A*H#UyX3BQRcjHmc1T`K&#er9qqz3R;1hc(aR*yunD74W2bXOvJ#D4 z$MlmEZ<^gvc6|MX^CEjYxCR7%W>4NOHg@|x9|n-#)t{}>P1W8UfhEUt2I1z1py?Nl zcIhIem$k;al?n`&3B%4HkI-9vm7(@8NuLnVw`zKPr_-MN#i#hhl?hV{T3!jXSV>q{ zqW7_+Ugyt+smr%I)4{CQNSFM}xg4#bK`%!D089X#1eE;LR){w`nWq$h=PpXnZj+b(*4`I;bOy1V;$6uoe9q!-+w$R=9^>P5uvdTi|iKV$_{57m15pvh3z z&pe`|(BO*(kaFDBtybX#JmXjgs58wB2gbH(hrL-@cA|bGc5S zP-?Onk`Vxzpsl*T$~zRV3erx}E{)Ics>YUUJUGV582_!{b& zmyu;CmN4NhmUhr>C5k8S_rUMkBn}TSM}{go((sJF4&yJ%N$^=49bR1u9(uNm8Elyb zucwkq)}c`X!GY4OdFI34lAHsH!JUsyQ!nwZvl~AF#v4$omcwr!T|Z;(f9q{ufMOZ5 zxj`j)=t$#Ny?bb$KIQmJj1dr^K+X5|wXq8P?bp(}Hg@g9iU#@ktC?th_N8csZ$TNy zvkE!}qoQ4}X6CyRu8QN(_S5wTuaWXcabAuU{t#u_6KQ1qDT8qxs#Xn>9ApDfLswAV zXP7U|Xx*+VsmCpynx0`tbffq$b=EW3U#`_xQt~hqD&)V|)y^r|uE^3kd_TjB5g#w^ zLKDcS)0YEfh$^{J9#D+~=Oa$b=iL@6Z{7W+!U-_V-Z^hxtv)(%w|u+mftHv$*Gfy= zovuGq#t|v)iDSFdb!Qi@RnnJ|8Jrd>Qku7Es<)ute)F>|^uXgEx4d|bek}dpw18Gi z9ayWaI2~bO2i)N>i?CxaC=4`W-9cZ9JLyH}(@Cj1MuDEJa|XZhagU(PZrki86gX2$ zU-U0h_UY2*!GKiLuJc7LDs}Ny9Xc$B8@Sb6Q}k)|P)DNv5GV6DWy>_rKl|sGtIPVg z93}~(W30YM{$!B%xyv<9+CQVZo<-k6c(<~}dbmWW4moWOXtkll_z6E;9YRH#rzk!; z9|C^xz5#wgangE!-u$5$sFxRo-kcZx@Zn3v4cl0J0Pc`XvEnrO$f^0bxmL{l51_6i z3|{MvE!0Hd*yKkz{q7l;rsaus~f8^Rb}T8L8`3TNi9Z#eJ!{ST$Vg0Klx zZv7@b%5(B%XCdAR z)1KWBMbbWV=T*dLBZGm_d!!7mfm_^7)u@^m*Z(a7kl}?31jD-=3rJYCc{H<<*@k`wdv#%XvmG* z4C!jb2wYL==F{iXm9}E;=QHf?m$^wFL+sY7wKq@gem(SGt`@TJ+fSTl@>7aM9-V5i zy3>@;Fc6H+t6~~_)-FI=fNl#4-xK2)^L@$`h?ziInF)I_Q(K|a=$ymaGNXX(RhaW` zkH?9~gC6!or5NJrNlzSL{nUP69-U!*ovkAr2%l#_oUy%L^rT6}(nm-KX+M_3&{v_? zqfJ<`)0MqL^P#TuJP`cPU;>E}-H#`O++nql7e@h>W73wF(yT^}O`ylrnu2zZF3e<< z$4egnX6d$joI^FpA>#_QQp%wom)4xNZ!7Fl3NM{EbF`>daQ`mqrj@~5xhf>-m^i!J z_>6jVX0P&s84)7MKVT1Qn<~>XhSL5H_$*6F30s);)EiAdNfhnriBpV9N^bm;zUF6NLv^V_SSxKBtwYD>YoxQU+?beE#DDeyJ`uRTVf7hhvyb25SSdRWZ%{+tZitJi#gr z6wWnY^``12q|IID&3Y1_)4N;G-hk^5mK41cz*gIzAaxtNigg!5s}J6zc=0xcIONXo z=#F&ytK6T6oWZ*pdN8C3X07;8qmXbm5Z`2gFSpEVxO!!Z*kltH(8M7Au(^Xw$*@(s z>|pUOZT5A(i@ap8ZsR8tLoC|q@=Uw9gBznwumwIcUyz}X$+3AXB}1>eZtcFY++e6% zNM&og1rq4T;u@ldjnbp4^?*FrPM zXm{GL ze?9y^Oe^UgR|l1=#*US>q3*j?K3Lmia}c+?VT^va7|ldG6I$bGGFvsG#&K^YXwvG{ ziRV`D&{$)JXY-&3M2@@wZuOn#?UWQooz^lBcM7;n59c&@~cT_6<9hDJ7XtLqq z3Ay7#J;fk432EtZEXT7!w=M+xZr~P8`@z4$(q*UlcLIi()@eqipmil?Vkv!({Zdj! zy|_?wM!;LvT*!Kt9sBJ4Uehus9Iu~vps~C zn3)68DhV1%nhVm(?`|(^2n!j~Qlv9;0A~k1Y5?p}l@h9F2Lh)SsS;(=p4>~(uhn-M z*Y}QE*}B6e){36zS&!_lS`TIt2_Js`gMS4~)~60qKAoJtpWGfvJA_m6^Du)uUafUU z!7?r8-+P}KhgKVhB|`8Hc@AstI_rm~XZipC#I*zP(0( z_WF&cG=Dy`X$`}KzO0Uczs#3ibC{56VD5l7^gT3?|Cf-#?acn|^R6CA@#G=f@2o#9 z%rlYF@j(qg9yFgrc+rPLHMPGdLe8W_>t1TfB#Pj#)0f$j8HtHV{2%ND6CpLBJ*@z2jr&)o&quivVF zz5Sws`(ADL4dYg6YDlGKA8p|pLDd<+A-I9+9__zHiFMl-hxE>a3`P&{DG5hGq)@g0 z+X};XlYFs+S3Jv)lO_(Lf-{G8%}SL$^OWHBD*N@Xf@EX2h>LJVHF5nY*VyvaspI!R z{yk3!u3nxk2~8;f?qW|4!ZhZts;8@aA(2~OgKu#yVNve1I+aEq2Tq?={2Req$R+Ke zn-t*hH&(W9kKcm16pwF{T18k7H_hU|?06=t@-0o+ghb=W;~*Fl#IY%Itcxz=c+{{G zztng|3GP_17r3Q!tyWy%`kHWY$X5R|;p%thy$6}mzMif7ms;L`LK1yNBw-@!64hP? zuCm`&luH-Hy$fAC2fr42Z3#3Ksp*Bsi=84@9ot-9y+qT-==uEw zy*a<7Vn<>r4R_tt2FfdSLH$(0!VZEfCn66|>Z?v>DQhJh7aVCFhl2=_-`2f*lj}OP}$GZ zoQ)V?ugn7VQ@ezvCek8>pUIv;RqK@$_6NSdNixv>tfOEgYvlwgiagWJYIY64n+#5YAQ&n2k{S{gf{|Vr!z&a{a_qDcGfgSW%i=6b(ivE(gIMQsY zb)6p2KW`i&8O;Y3vVDlD>XE^Wi2ZDgGp(u8sb)%bq&s+9V{99w0(3GA&|v7^(YVo^ z_3?Hp#>grw2-tsG<;EVeleNhTvNKiU z*yD@Wgc%lFM?Cy%l~ch?QRB0!tQo*U%!J1!>a`&Ts4=}nsZ$AmTeG|0$OyoQ9o^c? z%!|8SUW4cINH?7?QA>_K-U_1iaf^m>Tx^P$14GRTwjql|C-$^a*n_)R+Q>`sV;}ME zu*F1gPz&rrjcFdK`3ayt4{{}`o-n+^g?@-=Grjv{52hppm00|&_Ob943yK32zM8@= z0tQpr{f_Hj(DAidKw_<~k*s^HT@=f70_q3?%}J+k-q%UF1QU-_C%=wb&ba?#rezK) z@NORTZ`BX!Lb@5!1QoH6&VE=^inbKwF|M?{{IVH++OW5WL?lU>kM1xeOF2h zVQtoH|Chy;)y4@FxpJ&IuUrY`ip18aNt@#Si14GS_7f-X5y`(4n~XE@Gui1IbzJ15 zI87{tIj;g(gUcYk7ago)?)0fiNoSuNm~-N^MHu&Jv}&KY6=`m{ddpo%${gR4v-$y5 zKMUzV5veb)-4erYm&6DD6!cY71e{wH_c{O}k6I(-W*L>#3{S);I15Bli z-HCsZkXsp+2&T>RzTiPBq;aJ6pD6R_5!#)+QR)%((al%Jr@5_@UU8ozoc(p73e9~o z84P>Z#3W03R6*5!~;-6RD^CR=f06JpPzJy7FAs zxTv)1>wY@_hdf)QFScHb81cG<0k7=Vk2~f4a$1#C-Ml@Ga(U%O)%JZkjlY!%GjK}& z2)v24y7A+jVdaVepm#Up)@B`uJ5E=Za0R73H#Yz3nBBVdV>yN(W6IyKJhueunB!rg zUCoP_MhP>?k|GM4C7+ z)nB;~Ru83P9r-gIaPC2R#jIY+2Mm?_wIw)ms@>d6FNXXgyD_&RrRKIn(-9DKCNXEw zxvtDB@uf5Bxgsi7OA#AEUW)k^x4?H_@jV@AmCO2g_hDAOkD{8uEqnAmSGBx=^*v$F zyJkc>FHAHkG6?z)eYV0zEo08tJR}yW)hF+bbp51AkGpJoj3%m7ZY~NP%IH78R~*H-#3Gc zWVqbm@hPI24i#=G*iU4_%_mDAcp~Y)bY;XDKuR_*fW10kyjKu83j*{m&oc1($?_WU z>h=Wl#f`*Kg~^zyrUYmX$KUKez1D6*z01|K*2}+wI_}?+%&BPp zW&Cp+vdX^#Tsra}QO1}fWvk{dX~o9Blk^|?=IwXBew;<{kSR+|bK!^cjp#X1TBQ;5 z&iH_?@7kMd78Ln$JwMFLyFzZ2SA%H6p;s)9ir8QE&I(Ol5%OB!KR2j}qNXX4x%qge z!+_nXuw3Xl&#)~{*BhhPPmtNuA>ckT8j*`Zz#j~oh7iCD`ilMa%vNFaGxj1kl88FffI zk|H~~xfo#9T3k;zsn%_>rk2XAA?@^2h#X)}FX%EN9yz2f=rUo;s7(tys)*1H|yyN4Q)6>&1`_8MqnNG^+1g03JCu%m=k&an)kM1;PR`pG}%GO^V zK3M>xMpTTygL9|Q?vf3oYsR2aT~$AU$?E_d4f8kZ)f`ab*<-#w95yZg)Jmq(6`@_~ zT!S6fc<|=mPO`|%yZ`6%Xa3LN!S`_75DQVYvn7*&m00a|CQZNzG0O`obk4QoNNq1fRntx^s{V&Te1jCuq6LVYuuczPTDK7{%(a1 z+4Y;QC3$@a+bGt;Of_%N=Cw0ZE#(*@c5US5MX6K<+-Hw2C&{9Ie~;OVv^xnE+YkO;hHy!l)5Q^%zsD@=Emc8DO>GPYB^ZrbmguxsN`_9s5q4)1zpTwzOr%VGzL-z@9iK>a^HibT;m!;-0q1$d=#JV4DOgX|2z3$7VlQ0L!`T z_trG72IJ00+uTDgch>1e zdCaVUs|~^KG$$v&Q=zqvuLEO&*vz{P(W(J8ZICY$4`W)yXmJy8xsccC2*>PxN-lnM zY{uDpHfrO+Qs>6VMcobxO^4S9K%)GR=$!dvcgEzDhDczg;e{icyWeAczS?)z(&h+S zZ}zH!!>$8sE~C7OUKuAzvni`-TNt_kYPUEfVS*8{Wgc4{G`s|(sDDkM0*uU# zG7((Cne4S+x0⁣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 z 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 kimS{O&&+3Q-Hc`27pg45C8xG diff --git a/src/dlt/gateway/samples/sampleTopo.json b/src/dlt/gateway/samples/sampleTopo.json index 435d0bfbe..67115d4be 100644 --- a/src/dlt/gateway/samples/sampleTopo.json +++ b/src/dlt/gateway/samples/sampleTopo.json @@ -27,4 +27,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/dlt/gateway/samples/updatedTopo.json b/src/dlt/gateway/samples/updatedTopo.json index 17f927c89..74b084b9d 100644 --- a/src/dlt/gateway/samples/updatedTopo.json +++ b/src/dlt/gateway/samples/updatedTopo.json @@ -14,4 +14,4 @@ "connections": ["node1"] } ] -} \ No newline at end of file +} diff --git a/src/dlt/gateway/tests/testEvents.js b/src/dlt/gateway/tests/testEvents.js index 8d209723e..6e18e8032 100644 --- a/src/dlt/gateway/tests/testEvents.js +++ b/src/dlt/gateway/tests/testEvents.js @@ -28,7 +28,7 @@ const packageDefinition = protoLoader.loadSync(PROTO_PATH, { const dltProto = grpc.loadPackageDefinition(packageDefinition).dlt; const client = new dltProto.DltGatewayService( - '10.1.1.96:32001', //Replace with TFS server IP_ADDRESS + '127.0.0.1:32001', // Replace with TFS server IP_ADDRESS grpc.credentials.createInsecure() ); diff --git a/src/dlt/gateway/tests/testGateway.js b/src/dlt/gateway/tests/testGateway.js index dde6b3efc..b08f648da 100644 --- a/src/dlt/gateway/tests/testGateway.js +++ b/src/dlt/gateway/tests/testGateway.js @@ -31,7 +31,7 @@ const packageDefinition = protoLoader.loadSync(PROTO_PATH, { const dltProto = grpc.loadPackageDefinition(packageDefinition).dlt; const client = new dltProto.DltGatewayService( - '10.1.1.96:32001', + '127.0.0.1:32001', // Replace with TFS server IP_ADDRESS grpc.credentials.createInsecure() ); -- GitLab From 37977f225c2e3b9baf600acfa4ed32220f6c86c4 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 18 Sep 2024 17:07:08 +0000 Subject: [PATCH 93/94] Pre-merge code cleanup --- manifests/interdomainservice.yaml | 2 ++ my_deploy.sh | 17 +++++----- src/interdomain/service/__main__.py | 21 ++++++------ .../topology_abstractor/DltRecordSender.py | 25 +++++++------- .../topology_abstractor/DltRecorder.py | 33 ++++++++++--------- 5 files changed, 49 insertions(+), 49 deletions(-) diff --git a/manifests/interdomainservice.yaml b/manifests/interdomainservice.yaml index 9be6032cf..8926dcdaf 100644 --- a/manifests/interdomainservice.yaml +++ b/manifests/interdomainservice.yaml @@ -38,6 +38,8 @@ spec: value: "INFO" - name: TOPOLOGY_ABSTRACTOR value: "DISABLE" + - name: DLT_INTEGRATION + value: "DISABLE" readinessProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:10010"] diff --git a/my_deploy.sh b/my_deploy.sh index 3d4572e3e..67ec8615e 100755 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -62,12 +62,13 @@ export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_gene # Uncomment to activate E2E Orchestrator #export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" -# Uncomment to activate DLT -export TFS_COMPONENTS="${TFS_COMPONENTS} interdomain dlt" -export KEY_DIRECTORY_PATH="src/dlt/gateway/keys/priv_sk" -export CERT_DIRECTORY_PATH="src/dlt/gateway/keys/cert.pem" -export TLS_CERT_PATH="src/dlt/gateway/keys/ca.crt" - +# Uncomment to activate DLT and Interdomain +#export TFS_COMPONENTS="${TFS_COMPONENTS} interdomain dlt" +#if [[ "$TFS_COMPONENTS" == *"dlt"* ]]; then +# export KEY_DIRECTORY_PATH="src/dlt/gateway/keys/priv_sk" +# export CERT_DIRECTORY_PATH="src/dlt/gateway/keys/cert.pem" +# export TLS_CERT_PATH="src/dlt/gateway/keys/ca.crt" +#fi # Set the tag you want to use for your images. export TFS_IMAGE_TAG="dev" @@ -116,7 +117,7 @@ export CRDB_DATABASE="tfs" export CRDB_DEPLOY_MODE="single" # Disable flag for dropping database, if it exists. -export CRDB_DROP_DATABASE_IF_EXISTS="YES" +export CRDB_DROP_DATABASE_IF_EXISTS="" # Disable flag for re-deploying CockroachDB from scratch. export CRDB_REDEPLOY="" @@ -168,7 +169,7 @@ export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis" export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups" # Disable flag for dropping tables if they exist. -export QDB_DROP_TABLES_IF_EXIST="YES" +export QDB_DROP_TABLES_IF_EXIST="" # Disable flag for re-deploying QuestDB from scratch. export QDB_REDEPLOY="" diff --git a/src/interdomain/service/__main__.py b/src/interdomain/service/__main__.py index 4181fa73a..dc58603b2 100644 --- a/src/interdomain/service/__main__.py +++ b/src/interdomain/service/__main__.py @@ -19,7 +19,7 @@ from common.Settings import ( ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, get_env_var_name, get_log_level, get_metrics_port, wait_for_environment_variables) from interdomain.Config import is_dlt_enabled -#from .topology_abstractor.TopologyAbstractor import TopologyAbstractor +from .topology_abstractor.TopologyAbstractor import TopologyAbstractor from .topology_abstractor.DltRecorder import DLTRecorder from .InterdomainService import InterdomainService from .RemoteDomainClients import RemoteDomainClients @@ -65,14 +65,13 @@ def main(): grpc_service.start() # Subscribe to Context Events - # topology_abstractor_enabled = is_topology_abstractor_enabled() - # if topology_abstractor_enabled: - # topology_abstractor = TopologyAbstractor() - # topology_abstractor.start() - - # Subscribe to Context Events - #dlt_enabled = is_dlt_enabled() #How to change the config? - dlt_enabled = True + topology_abstractor_enabled = is_topology_abstractor_enabled() + if topology_abstractor_enabled: + topology_abstractor = TopologyAbstractor() + topology_abstractor.start() + + # Subscribe to Context Events + dlt_enabled = is_dlt_enabled() if dlt_enabled: LOGGER.info('Starting DLT functionality...') dlt_recorder = DLTRecorder() @@ -82,8 +81,8 @@ def main(): while not terminate.wait(timeout=1.0): pass LOGGER.info('Terminating...') - # if topology_abstractor_enabled: - # topology_abstractor.stop() + if topology_abstractor_enabled: + topology_abstractor.stop() if dlt_enabled: dlt_recorder.stop() grpc_service.stop() diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index ae9fd440b..363cda72a 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -12,10 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging -import asyncio - - +import asyncio, logging from typing import Dict, List, Tuple from common.proto.context_pb2 import Device, Link, Service, Slice, TopologyId from common.proto.dlt_connector_pb2 import DltDeviceId, DltLinkId, DltServiceId, DltSliceId @@ -25,42 +22,42 @@ from dlt.connector.client.DltConnectorClientAsync import DltConnectorClientAsync LOGGER = logging.getLogger(__name__) class DltRecordSender: - def __init__(self, context_client: ContextClient) -> None: + def __init__(self, context_client : ContextClient) -> None: self.context_client = context_client LOGGER.debug('Creating Servicer...') self.dlt_connector_client = DltConnectorClientAsync() LOGGER.debug('Servicer Created') - self.dlt_record_uuids: List[str] = list() - self.dlt_record_uuid_to_data: Dict[str, Tuple[TopologyId, object]] = dict() - + self.dlt_record_uuids : List[str] = list() + self.dlt_record_uuid_to_data : Dict[str, Tuple[TopologyId, object]] = dict() + async def initialize(self): await self.dlt_connector_client.connect() - def _add_record(self, record_uuid: str, data: Tuple[TopologyId, object]) -> None: + def _add_record(self, record_uuid : str, data : Tuple[TopologyId, object]) -> None: if record_uuid in self.dlt_record_uuid_to_data: return self.dlt_record_uuid_to_data[record_uuid] = data self.dlt_record_uuids.append(record_uuid) - def add_device(self, topology_id: TopologyId, device: Device) -> None: + def add_device(self, topology_id : TopologyId, device : Device) -> None: topology_uuid = topology_id.topology_uuid.uuid device_uuid = device.device_id.device_uuid.uuid record_uuid = '{:s}:device:{:s}'.format(topology_uuid, device_uuid) self._add_record(record_uuid, (topology_id, device)) - def add_link(self, topology_id: TopologyId, link: Link) -> None: + def add_link(self, topology_id : TopologyId, link : Link) -> None: topology_uuid = topology_id.topology_uuid.uuid link_uuid = link.link_id.link_uuid.uuid record_uuid = '{:s}:link:{:s}'.format(topology_uuid, link_uuid) self._add_record(record_uuid, (topology_id, link)) - def add_service(self, topology_id: TopologyId, service: Service) -> None: + def add_service(self, topology_id : TopologyId, service : Service) -> None: topology_uuid = topology_id.topology_uuid.uuid context_uuid = service.service_id.context_id.context_uuid.uuid service_uuid = service.service_id.service_uuid.uuid record_uuid = '{:s}:service:{:s}/{:s}'.format(topology_uuid, context_uuid, service_uuid) self._add_record(record_uuid, (topology_id, service)) - def add_slice(self, topology_id: TopologyId, slice_: Slice) -> None: + def add_slice(self, topology_id : TopologyId, slice_ : Slice) -> None: topology_uuid = topology_id.topology_uuid.uuid context_uuid = slice_.slice_id.context_id.context_uuid.uuid slice_uuid = slice_.slice_id.slice_uuid.uuid @@ -71,6 +68,7 @@ class DltRecordSender: if not self.dlt_connector_client: LOGGER.error('DLT Connector Client is None, cannot commit records.') return + tasks = [] # List to hold all the async tasks for dlt_record_uuid in self.dlt_record_uuids: @@ -108,4 +106,3 @@ class DltRecordSender: if tasks: await asyncio.gather(*tasks) # Run all the tasks concurrently - diff --git a/src/interdomain/service/topology_abstractor/DltRecorder.py b/src/interdomain/service/topology_abstractor/DltRecorder.py index ae869b7c0..22c436363 100644 --- a/src/interdomain/service/topology_abstractor/DltRecorder.py +++ b/src/interdomain/service/topology_abstractor/DltRecorder.py @@ -14,8 +14,10 @@ import logging, threading, asyncio, time from typing import Dict, Optional -from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME, ServiceNameEnum -from common.proto.context_pb2 import ContextEvent, ContextId, Device, DeviceEvent, DeviceId, LinkId, LinkEvent, TopologyId, TopologyEvent +from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, INTERDOMAIN_TOPOLOGY_NAME +from common.proto.context_pb2 import ( + ContextEvent, ContextId, DeviceEvent, DeviceId, LinkId, LinkEvent, TopologyId, TopologyEvent +) from common.tools.context_queries.Context import create_context from common.tools.grpc.Tools import grpc_message_to_json_string from common.tools.object_factory.Context import json_context_id @@ -44,7 +46,6 @@ class DLTRecorder(threading.Thread): self.update_event_queue = asyncio.Queue() self.remove_event_queue = asyncio.Queue() - def stop(self): self.terminate.set() @@ -57,10 +58,9 @@ class DLTRecorder(threading.Thread): #self.create_topologies() self.context_event_collector.start() - batch_timeout = 1 # Time in seconds to wait before processing whatever tasks are available last_task_time = time.time() - + while not self.terminate.is_set(): event = self.context_event_collector.get_event(timeout=0.1) if event: @@ -91,7 +91,7 @@ class DLTRecorder(threading.Thread): # Finally, process REMOVE events await self.process_queue(self.remove_event_queue) - async def process_queue(self, queue: asyncio.Queue): + async def process_queue(self, queue : asyncio.Queue): tasks = [] while not queue.empty(): event = await queue.get() @@ -106,7 +106,7 @@ class DLTRecorder(threading.Thread): except Exception as e: LOGGER.error(f"Error while processing tasks: {e}") - async def update_record(self, event: EventTypes) -> None: + async def update_record(self, event : EventTypes) -> None: dlt_record_sender = DltRecordSender(self.context_client) await dlt_record_sender.initialize() # Ensure DltRecordSender is initialized asynchronously LOGGER.debug('STARTING processing event: {:s}'.format(grpc_message_to_json_string(event))) @@ -135,7 +135,7 @@ class DLTRecorder(threading.Thread): LOGGER.debug('Finished processing event: {:s}'.format(grpc_message_to_json_string(event))) - def process_topology_event(self, event: TopologyEvent, dlt_record_sender: DltRecordSender) -> None: + def process_topology_event(self, event : TopologyEvent, dlt_record_sender : DltRecordSender) -> None: topology_id = event.topology_id topology_uuid = topology_id.topology_uuid.uuid context_id = topology_id.context_id @@ -167,7 +167,7 @@ class DLTRecorder(threading.Thread): args = context_uuid, context_name, topology_uuid, topology_name, grpc_message_to_json_string(event) LOGGER.warning(MSG.format(*args)) - def find_topology_for_device(self, device_id: DeviceId) -> Optional[TopologyId]: + def find_topology_for_device(self, device_id : DeviceId) -> Optional[TopologyId]: for topology_uuid, topology_id in self.topology_cache.items(): details = self.context_client.GetTopologyDetails(topology_id) for device in details.devices: @@ -175,7 +175,7 @@ class DLTRecorder(threading.Thread): return topology_id return None - def find_topology_for_link(self, link_id: LinkId) -> Optional[TopologyId]: + def find_topology_for_link(self, link_id : LinkId) -> Optional[TopologyId]: for topology_uuid, topology_id in self.topology_cache.items(): details = self.context_client.GetTopologyDetails(topology_id) for link in details.links: @@ -183,16 +183,18 @@ class DLTRecorder(threading.Thread): return topology_id return None - def process_device_event(self, event: DeviceEvent, dlt_record_sender: DltRecordSender) -> None: + def process_device_event(self, event : DeviceEvent, dlt_record_sender : DltRecordSender) -> None: device_id = event.device_id device = self.context_client.GetDevice(device_id) topology_id = self.find_topology_for_device(device_id) if topology_id: - LOGGER.debug('DEVICE_INFO({:s}), DEVICE_ID ({:s})'.format(str(device.device_id.device_uuid.uuid), grpc_message_to_json_string(device_id))) - + LOGGER.debug('DEVICE_INFO({:s}), DEVICE_ID ({:s})'.format( + str(device.device_id.device_uuid.uuid), + grpc_message_to_json_string(device_id) + )) dlt_record_sender.add_device(topology_id, device) else: - LOGGER.warning(f"Topology not found for device {device_id.device_uuid.uuid}") + LOGGER.warning("Topology not found for device {:s}".format(str(device_id.device_uuid.uuid))) def process_link_event(self, event: LinkEvent, dlt_record_sender: DltRecordSender) -> None: link_id = event.link_id @@ -201,5 +203,4 @@ class DLTRecorder(threading.Thread): if topology_id: dlt_record_sender.add_link(topology_id, link) else: - LOGGER.warning(f"Topology not found for link {link_id.link_uuid.uuid}") - + LOGGER.warning("Topology not found for link {:s}".format(str(link_id.link_uuid.uuid))) -- GitLab From db324b926b31a8146ab4b84673f25b83102d5eba Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 18 Sep 2024 17:10:34 +0000 Subject: [PATCH 94/94] Pre-merge code cleanup --- src/interdomain/service/__main__.py | 2 +- .../topology_abstractor/DltRecordSender.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/interdomain/service/__main__.py b/src/interdomain/service/__main__.py index dc58603b2..7ab9682c2 100644 --- a/src/interdomain/service/__main__.py +++ b/src/interdomain/service/__main__.py @@ -18,7 +18,7 @@ from common.Constants import ServiceNameEnum from common.Settings import ( ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, get_env_var_name, get_log_level, get_metrics_port, wait_for_environment_variables) -from interdomain.Config import is_dlt_enabled +from interdomain.Config import is_dlt_enabled, is_topology_abstractor_enabled from .topology_abstractor.TopologyAbstractor import TopologyAbstractor from .topology_abstractor.DltRecorder import DLTRecorder from .InterdomainService import InterdomainService diff --git a/src/interdomain/service/topology_abstractor/DltRecordSender.py b/src/interdomain/service/topology_abstractor/DltRecordSender.py index 363cda72a..5c53edc59 100644 --- a/src/interdomain/service/topology_abstractor/DltRecordSender.py +++ b/src/interdomain/service/topology_abstractor/DltRecordSender.py @@ -77,29 +77,29 @@ class DltRecordSender: device_id = dlt_record.device_id if self.dlt_connector_client is None: continue dlt_device_id = DltDeviceId() - dlt_device_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member - dlt_device_id.device_id.CopyFrom(device_id) # pylint: disable=no-member + dlt_device_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member + dlt_device_id.device_id.CopyFrom(device_id) # pylint: disable=no-member tasks.append(self.dlt_connector_client.RecordDevice(dlt_device_id)) elif isinstance(dlt_record, Link): link_id = dlt_record.link_id if self.dlt_connector_client is None: continue dlt_link_id = DltLinkId() - dlt_link_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member - dlt_link_id.link_id.CopyFrom(link_id) # pylint: disable=no-member + dlt_link_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member + dlt_link_id.link_id.CopyFrom(link_id) # pylint: disable=no-member tasks.append(self.dlt_connector_client.RecordLink(dlt_link_id)) elif isinstance(dlt_record, Service): service_id = dlt_record.service_id if self.dlt_connector_client is None: continue dlt_service_id = DltServiceId() - dlt_service_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member - dlt_service_id.service_id.CopyFrom(service_id) # pylint: disable=no-member + dlt_service_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member + dlt_service_id.service_id.CopyFrom(service_id) # pylint: disable=no-member tasks.append(self.dlt_connector_client.RecordService(dlt_service_id)) elif isinstance(dlt_record, Slice): slice_id = dlt_record.slice_id if self.dlt_connector_client is None: continue dlt_slice_id = DltSliceId() - dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member - dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member + dlt_slice_id.topology_id.CopyFrom(topology_id) # pylint: disable=no-member + dlt_slice_id.slice_id.CopyFrom(slice_id) # pylint: disable=no-member tasks.append(self.dlt_connector_client.RecordSlice(dlt_slice_id)) else: LOGGER.error(f'Unsupported Record({str(dlt_record)})') -- GitLab