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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�cCgb#m`a|c{XyiMM^|2OP(X?bxKl6T=|ov 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 JgTXZr4*< \(.*\)$'` - 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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≠1#=e__gQwX=I>U$~RAL}$@- zug6oK7W|uD@N(YahXocgeC=@R&cV0wQnT7Vv!CGHwQQnr&HwWgC%?OG!@2M@$GqK! zhlLbbp53cFZxepv{{8*RcA4dU=kHVKYM9)*x!GAp1<@v<>FSkRje*uG~?aMg@!9{-k9}Z`LeI3z8?}6Sqkqv`t^+> zlUmp7hjDA&SBMqd{-Y_wG;M)-&BBEu+HwlYf0$__prNddYzLysrmXz6CJF8tX?rcl^F9a?SVD z>l>f3)nu;=tT(aS=>KXq=lA{I-`_AYn5peze{=7T_@%uT$G3c$bxhFZG0%dJm6r++ z_n9ZXIo7!&^WlJ@dmMDD#s3#4?5S0@Nh;eNO=V8(8}=Fgk@6XwQqtFAUX@AAtjOmIKf z+UswcZ?13+u#w@q{b73IeCJv*gNJiHzm#6FUo8^v{`=a3*lUUr)oWTlWU%P`{rqr7 z*_30Rwa+$peU=K`vwh*mT{^28BKN-(u<~guRoHrZUDM18pT!^iAE$-BX+4y6LsYh% zb@IF2%N;VFazCAtbR(#`Snb5-c9yRG!xPuJuAZ5H@XbmM4q4|!(@fdkU`<#D*aEY(7`j3C}mDTU^ zcQpxolRAG_>iyO7d&d8Le*XSle#HFmxsR24)9t74*37wg-!4z@ZZRa6{&`yP^4~kh zyg$n(3LCvYUh``+U-#wD4<5X5`Y)T$x?0ZJ@ZD9RmeuvH3swo%xxKs>n|**~amo3V zyue-gVvGJC^ZH(Ay5^JR@2xjA#z813G_OVCbA_D-mk z%QYt9xa)_`y|4T-Ya1VPc-VuRSKAGYe0vvm_b0y43Ol-w&3R_Y{x=eBDcOdK&y!2O zc{!c`5GpabjHCH_hJl4|v*PO2h83ZrY|B?0R;-mee7XOSZL+-8j^&ROx2yaRlXIRQ z>Yg#1>+rRp=qzIe-&V%e+L@8Pfiu5yXs>fBzyC5r+C==_?fr+AM9Ie9c9XJ?jW3HU z;rnN5wM)MD!&24Bum7%^!LeTaX^r_?-ioYQDu!`W?!UZu;rGX_<_2-z&(AEW+~xKt zo_Rs_>rm&vD-A|IBhw1i@n>4cZ%!RNq^2Q`F$wur5)p`#deJ z-{)Ox=(+!eaMZPf(>%XR${#+Ktl6TzC{g~CP|T&i&PBT`?PMp1uG3$!xG?KtQX%UZ zwle}xYkq6KelRJTxj1lFIzPvTx|_``K6}=;)qdoRIH%jbDEia;OM88{aiuLP@qc?w zw!OAaG-la8w$18?6>Wr=UvCIc^t~;jzu|LO_o4l+>!i$%e%%nUPu%G{3x~hkp}IKD z-p*eq(vL6ste3zqkdYV{!#TUf)FAt}hR&I_7x$k}*t0WN-o!p`>R;Jneb;xJ511V5 z@2~GXm>}W*(=}*r{?pI$QgsqGufOEiz7zhq&;OO?mw)A+6Q`}=`Yro=WA3pTdA}FW zcvoJ%zx+sv&VQvGolU>*IVT>f&AE3zJ~sc#>2ubz`mavuJEnQ+;U7_AE3wLV_jov( z4N?!!eI5J!G^qN@>wYJ5e@Rm1%D;L1)8AP_s=C><;dP%if`EO&+J(EHUrXPCp z#ZybxDYTbkW!4dwEa3p=tmTV~clI54rTt|eMwn}j>Jf*p7VG5-EEdHN>5>HTPo}J|4S5)$9nhu zQ?I_*zlrN__-aFy&2o!3n-;`w7SBDV_BiqTN)C;6j`u~^?5Z_wTf6e$hpkaOORqPF z$sRs1RjViUQtdxOo3|6Y{0?$IjQ^(Fw6!F!WL{io$+}PnhPkWlS8K#C7G8bd_~P8o zFL_zt99ZUh=Q7{Pe0h|mqO4xSXIJ@UvyR($Z<;1u?_RsyZtoSV9~t==7eMyT7YbN0g-(XP2SzEuUPg?EI$H{Ebzy_@!}H0deK z4`)i;s93)6YS-TlC)YPiPCg&G-!*IFrP|X^trN{k0iw`+0AVZzzlAdbl#e!hH|h^e^J6|*ZJLNm;S28!@K-lUdFwcl@jzaE_F?bzUTL{%Bcl@b&{vl4=t#$ z;oBCt{%}|5!T9sXPA;j|UGwRM)W)vw=Y!+VA8~pi#{RsuB=3#Qr7gDBo)!)NKrpFz!I`zrC z-ZkQxWlzmJ9yTBQTL13vw!wafb3e}9Y(FR4Dt%7Mc8AX&RF>{8%qp^|7meBV9JQ5M zU{PN1dwSclhkqE0n$~4LVY&amct=Tn{?nNs1s>V$DE>Ra0#pyCL_C`NxVw_w$-LkC z`u(@?DiytLIbFQb)OG!#n=4hP`0UER%=-6IoKwbAeU79>R>E^uD{$TR*^|z+MSpcl z-jo&p^IpcS*>_HB{^cJLu0Ir{ygjapw!Hh#c~O48`;X)Ml^&fCbqR}gp7P2lX|=x6 zzGqsGR_7=5Z4-PaZP&KAs{BS)y&5>aK?*ADN}a6IUqE942#gM^9AC6PaVek0*bO_))|X_NU{dl-gb9 zxHYqE=PnP~J1_K$`GMYdtrwPF>!+z-*0d_IvfPrRhm~w%pr2 z$+kh^Zg76q>y>-G{>Hj2cxH8|FV?N#Ww^wo=(dT{-;}>Rp3D*U-|O=OttPF~4+~$v zaeSP3{QH#`50>UFsVm*i^Iv#Ibq&7iL}ra4_985?)VoO0Zp zc(Gi}>YQHV<@c9tt;^AlSd2AEi2sg`NZ|EOS^+=CLGYXyZ%3mSn(+Zo5>np-_s7hPPndfcXQe6 zS4X|RpYzmr{#l{Xay%(f_^Z{LnI-%CY%>n5dUO4dR=D%^6>@2xSFV5eQJ_loVC6-> z8RC~LFITDE)&BJQXm(a`4D0L}Qv2DSPk=eJu7r(s6(tI!=<;%SMqX|3q z?E7=+x$Pu>({D4Y^1HspgzY{WG(!UgtOG-hT@o)%Gu${2}t6@IITH8t;yG=>LdZ6PACgkMm1T&E#uy z4qLP9J-!s$yz%SYId zl=44&vtfC9-T~v!_guCgeZTPcDgF!AwcKp!`GHH3O_!kdy@tivJZ)!A zv}U_Wwk>;q_=b+!QTf9YOs7gr3+y$N%y@4s|*rCRoO0GG7V?GK2=UM|7JR>dm)b(_FH$UTELt7Z?gZQ1Y-W99svCK<;$z&3$kyMp6ZvH~ zygn$`k-)F?WAmvWQx<61F6Cs@+N<&K_a&<^-T8N&)$Z${%C_$oWSmNIzv)p^7F=WLB8Z zUt=F;xj%B(`h}0L94P#|++cnv*Ta=89Bcn32>xnPxx4vsu(g?(`>g77ochi`GdNn@ z7bVDU<=p1;Nq^ae9cNzDoGHFipl8MS+I9DW7ZW)arVGi4g&gQPmJ(JHv#amX>!=8E zndaOWDG^z@_SRP3HXfPAa&126;z{1$&oXQDN;Su4M*rU!og$@}=GM@+uKgEy>mHE#a@G@Gw?*X&KdJM*QFy!z(Tkv_?$x^~Vr z!&xh0uTAT&(E7LJ&Bs}nm4B){+gB5Jr~TlQ>adspzG;2kn^Dtk9(%x3s$H}9%eOzO zwskvQlCyVNZYvfq_0aeeStD^^%lzH8dynp)Qt<6pHT$|hll>lVDls!(xbyf7cm1iy zG*3SKGZnS*JBPi|?%n;GKkJin4m~WeSa#>%(uJ88^$l|tWJ_5d{Qh4?@9zC-&%2xl z*BxE^`j(Aief|HwEw#U{ZM9Ee(4^nM=qW8W@k<+ukGxY0N zxy<&p_sPn&?VK67D?Y4e!(#iyMN#bXYQe!aSAtqUr*Ym2+4Vnm%|Gc!!E2l6YxJ8R zioV^T`Kr63=4rG*o7;lk_r)2~TbS0Wt!=tI!kdr08ahc8|{k)<5X;qr$sl!)Hp+9I((i+v7T>tc1;%l6xam}eI3_iA-K z-Jf(ps%ma%^S;&mi_32r@|?Hj`@hHe#gl10>XSD=*#BFFY1PYJn^t}D$y)yU{ox~( zW&5_=^G}`NQqCp(@n*K?7V{48z&*=Z!i-+V-Eyx=aC&um^D@r3TWUSw<_GtOwR^JH zxokhG?{v{z;Ji;%3*+VMi9JHDdFIf-%lr%tk0PCj$8HRK2_l-)oRW^eVFD1-D~BY{OC$1;(vQ z9~*zXUw2pCVE#_oLz6!W++sOk^vBg|z1EEGB)hM%A?(X-MCT-#Iozz|n(J?plXSmj zt;Unhbs2kpX3cnh>8qw40qO`QLKM(tX+b<6v}8=Jho z=gKuMU8;Mm_v3+Wf3q_DV<#hbFaPWFupJf_`L%M#bN2*zomywjrCi#Px`XX z+5B78mp|H-2lkhyPB%zCTzTZlzn&9s=1&axuvz1Tc+UQoQ|I#+JUcBdku~LAr2To7 zIY+v!>zePq*e7pMD9fDYQG376XIfg|{xz>wZDF3c_vt0sw^eHM4;8PEJ23UN`O;tS z&zn9xI(K@ejrfF!58V;6_2;~wPyGB)Rp#Flt9`#$a(1@vzf*jvcKwvgzM1c1esCFF zQ2#Oa!k+4xpy8qu5C2?6&&Z5B^#f)n3hid$dLeGSY% zdH?Z#ql@|XE!U~PlyQpNRs8qy;iu<+=5gWd+*rH6!^PM>?ReTTrp zc7Nr7yDDCHeU_z(p0T|;DdoIM#x}n-)6TWL*dMs-`(l=i#*eF{P4;DTg#Pb3#~s%+ zHQVUV^&D4+Uw#$oZ@oGl&9_Z#p4qb7I$t(0FqCgrp7!V?W1NKKPL6fnb_Wiy-Zrc# zWIeX#R`*5i?+IsSHA_scw8lRtc5j@6FmbvR`;bjLQEAq zPJWp>DM7#MOUe8Q(H{+72l}T4rB-?d@UKYv-X1zX{kwnEd&j>OS51?eq-KO%Ww<~6 z`{BeFDO;q(`&Ufof4H+Y{o&-j(r@pkEzGTua;y7c`Ddofd98bw?SwOI);zMa=CfXB z!M96%&8fdExx#lea^gIK{?+T>t-j{LWdHj#x6h;Rt1_LA6w2PYXT^4$@tuIq8^wt2 z&CStFQEP7TolY`svEe=A@-j}_>wD7D-|0yUUx=_h@3@{Y{p6DB#WMO{-+e-^A28a_ zrn^}?QFwxG*skp*v0T%iRwsJCo$_;1gI+*=S?kpd!*uN_waX5z`ChVR#!tmJD|#bd z&O7;~bxo6<@Q2rl!AlxXFEWbwGS{!x?SN&Ou*~(adsE)cRPq0P>uKi2tUAdvzm_;v zWN@uf6FZ=|dgrCRDwnu_PAXZivB*$le#6f%YX#dNLl}nzhC?;S@(er%zI|`Myg8p_seNq->1gkarkhnILTV=SGj3l@7}c27eAj@XxZi; zQ4%II`K96eXB%D%PW=1%`*rEB`z8Fpie>Dp^?8{vr%C4AeMWe`1P|_33QYLTwAJ#M znhali@~O8d(=e6qF02Z6%K6=XZ9_`+L4} zr`@~BC;q;f)wYfiG7H1!&Zc+wK2j~N&lD+r&w+com_cxqQ^A{td&ZXq|9s@SLP*iJ`2gz&U4PV7uO=bQXnFPcfI(P4|O}m9HspePG!$eKDA4@BRam*aAjmg zMGniI^ry-hXTy3T|Jb?N{x@Sgf3u^`*nCm;CGH9v8|i+w<%@+^&$3ry)4S{TxHB;L z!qt2;xv%ru-S<3yymEnRMb#&+65jJiE(h+EF#4R@Gdr~BTJ5pqkRLier*3hcZ_4U9 zZ_^W)$!=)#m!mCJ_V9%_$uB0CNhovoA2xojyhTpFIX&%Z;-|*V99K*ZIiFf- z4_6FU?rl6bJE)YIah2P~Pqvd^-rahsS@4Y_XIcJ@c(sHgo5^o(nr2OVw>MMv_iI)= zQ}YL_ce2U%%1!?EzbB7#b;9-2vdPo(+=6aqE~zUKz5G3<#s6R2mM=5g-5(bjto#zq z5^fq0Z{;I!T9=QPV{w`d*)0OAB-^9ZEeX2AT@}SYQ#O z6xWdiKcDsC!-R;wXf4uk~t%p?y_AEGh*Yd7E!dJG*ZF{{XW8(&%Y3Kc#FQ`iO73Lo>T}SaZ$Djq^l@g%`vY2c&);AJHQ8m= z4{N; zGC}I`@#FO^2NOJ&-O;&RCw%wdWAj(P{A{mYNPm?XdGJ@B*Y_opIZoTO2k$rOZ2tc( zN4RSFyg$=LU;PfKT59m~pWn=RosZq^_9}imtfBSSSLEAGyY1Uk|EBM&KY2swMr1MX z;b(L9xlTzJvR!@Pe$3;4uZ?|Y%&Fsx6@GGDz1O#D!h>(Q<;7J?=NxNkO+Pn({|ftD zo2$_aD!-XNjeLCNalt>m9VO?HCRxulH{20lan{fbBo^%UcXq))22025CPhv9=WAt* zPe<=>sjrb}xiWYAy5!8?TmJm3PrX&Xw>akHQvo^2is!$U1pOqK$?I8-%=a`-(~UZG z=nhwtZ}u0Dh%Qa}+Gd8*#VU7`0iib{cz_q1GjZ{U3D+rKjx&VvOT|+;8JJnmX;wn zBfPVblQqNGOmyDqd+rGnxz_Ex`XMHqeLe5j4|OlSEB1dB6Iz`zoqhtgBpg z+}}&CUzmQ%Tf*RTDbKvUlFZNdHc!;{GR%4E_hU`Cvc_f+{$5_k|2J=TSk|x!milsB zdSy8A-0A*xl?u9lvu8huzs_<0mNdr|lMAIg`U-EU^{h4SxcdC=%PNDP_vXfJ{ywkd z@1G;T-LLnolb?JoGV~Y2u6d1D!wn-ZcNFG_9j-g#V)IO}f5!Ix4tM6MwM;cttnz1` zWWTRtqxXHs+kzh@_GK6=eV^|>VHc;|R-Z@d{MH^*-!W_7jJLmVxcS(jCDr9?{ib|4 z+OG7E<>frvgo`!jZ#GNBnOe16H&48??C^&7_Hn=R4xW+RrhNE9)zz>Fv2f=Tmz`#Y z^SdV6%5a$$%=#%eVhMkN+@#I6*OVm$+(`mD=QYZ!`XfZQg!xO*NBxqi^P3d9Uv&5!Tz= z=l1HVRj?(je>Q)G^<@Q=y55kjS+W1po0k84YqM@`czj8HZ_vBCkMlggFO+=#;qm!N zCG{V>jTOOO+h?eJbeyyJpw=&(J;@r&B^K0+_;Gbm~bn}1oPLIzg zuUkB|n6qCPJ|PPp*Z!=a@SQcQxW^Y%kAGHIRDUtc&F{n0o&A?LUVj^Z$hqSN%fpL~ zEnsnwZD3T^#05z zEoXik&Y#PEZSPiI*<0let*xpotEasX+|F^Z`XA^2S!@+y-cyf8npUdi7(blXd8a;h zY4v5T?jzU~d*2zpPBbr*bJNp1`r=En#EwrKZ0qb=7D~EbNWZDrb7JMu z8BDxtB3Bh&ERFJs*v9(S{n&x@n`tcT*B?7lx|KsCBsnP2^p6pL!k<^M5?i;h9Dbhb zGJnOGu0GBAV|s1#g1Podr|!uM7X46X35`9Fa@MrvYRus$vsQn&V3x3E zTIdY(KXv(gGH%Aaoag!NU#QQH=_(b0yX?~(4DN1!aLT*l%}?>fd*`_yFMFn3`MUDR zJ0_>oyc{m+A(tFy&vCo%vBHc<#{jECRVPxm zM19+*a`*Xl$C>ppF}K85Tul1LZtz-+ePJ@kn`Jo-wa*MSTpj1Na(?#-nZD$=S>@FY zyk}ZJWU#C`rX#qtnmg;T*4^3NqR&@9^eFVK6hF0GDB)hcSd?A3(#bbT^P@B(rq<5N zUz0yud`m%Hp0!TZGN%eHzH^@W-Iw-mirl4t#X)0Lv#(xn@iVa*w!+*GKNj3@+sE;H zj+W;x{b@_8xAiYt(Dp6(VBKBLm6!IeESVq7>Alg&cB`%x-|;VtH++oZUg)c!Q+2-O z^c$my&2}>i^xb&5XB}Rf@n)K$evrMQ0NZ)jZqeU~)BOK#*e{{*ZTYgg(1J6!*uC$+ z47g>{eZR4S-RI!L^L`8elenz!s(R|ir6YXsO74xt)%ckxQ?AzBW-P^XBJI?xl z?)D>-t1NH+%HtG%blkRHZ0h{`YXbH!{w1ZfsqWZ>$mJhP?s(b=*9+NB@?Ug*|BAZ% zGTUme6fZA)x9CZq`KyK_hvHo}-rpiS`Ez9HU$?nm_8hYJGgT`1>c4$U^7_Zzb<6gx z5N`S^IrX=3)tzhC_s+SuGCia6mzU?$sroNO)_1CBF`d;tR($N`v4?-|;^~FYvFC(W z({nBw=lvIwb9(Q_pz>a>#h0@UGjeU&)~r1`;Y<|IS)0DZE$6g%v&Am%diY|SL~{7MqZ(P(D!#3b ztND{d%1nQ)wP-r{@~XsyG7e>9{=>H4Iez(8i>`H3NjP(f-_U5gTyydMBMaV~H&FY} zy+gE??fcjHhimT^`qX$b&)>rT;7YcMjqr2jT^nT9X>UK2`?Fob`gv%NziwM}5hI)X z(uXS-xK>=LW}GJ>*K^MQo7-{6buV5_?%_LlesS^@Et@GbnvXAAe|&QBwS~7IgkpTuC0RXKKZ&IxtaIL7{rHdfBp-yYZO-=<-V^Hk zVI`B(i(P*AKI*>tn|`Rw!yu`fFDWWhIq6ix2`+^-G3^^RvFY3>=Ur&I`@thC(OU7n zjhfZ|N)FY`c|Na4KCeW6M#1})(^kFO{J5)r=7arnmEB(6n;m+6d7bU* zFCA;HC;yr4{=guiN#!p0w+(Mzb5D28KfGy0)EblZXY3i(u5R}FzIOg(trOh*t&&TB zFFxk=J!r-Sy(ifTp$pu0?~%H}`qZ&Ph3A>F*rCjyaWlk&X8*C;_-JoHgtFInKhD_; zYmDTctugx36cm`2UbM#{cWUd`pryYTRo&PZ!T#J*{?H=n$?t?8%-AB?H#0P7X|>h7 z16tQF+ilyKwPoI3(X_i6bNXF7F71__{BGtKy|CHWFG!eNvio_pAm+QKPTCZQiZ;*h z!7eZ7Z53ZyoppRk_3|%^Hv<(hr2SHd?+J##h9n7O>|>5(Lk zxz34pS%Oh(tyqe`IWVohcmCJn&s(*E4ko^pi`b#&koc6NxK&8lIl17?>SBw3!IR0LPFToYb?^BcY4`lDw?+$(I`#n+gt-VFf(Hl&U zvcqoP+rqng%lZ0@8HnKDNT%SGD+2d|$y8QP@NM3J(<#lH8 z>c@ADkSctQ)z@W{{#LxXcswD|@Bh4wC2rd<+?=uLvGi><0&bn?}3!Cx1@7jQ(jwL4!nNQ~4} zGia94g`O@TAr4Qej_HhBNCv7z;~z|T!m`}FJB{)^kYUiekjB63~vO>31;-1^4qYl1tL z${#y%gsDXGMf1A2&KGjC`;IT({Gq^w&5C8cZ$Bp(qEbL(~ z=XpMF4Kl6Tc&T=uRM=0`jOo$5zYoM{ShXL@xxr+i^P3~BqX6$s()rL>*lK5-F|9a$T z7_>Cs@&DPG%j*3iO1@p6r&+$f;=Ic@M`QoR3Aa^avUh0SGcSI+OU`~yw$oK{^9ehj zD!HArRt`Hc{q^efPo6GM=QQ8DarR|=31}V47q5xiHYfMat7p3(T;Fs0`j?7#Q@N8& zYcJQ$TlPINT5qAJR!P5pUCy8B$M=X|R(`p}ZsCXLo3BfKdA`(mkLWD_)G21iG><*J z!%JjkKCjLw==TN{j>%o`W6m3W(_f=vpSSLhgqoO>giP)8UwLw${@zK4w9mOw+vmI4 zA*(QD{(H9js{a1)Bf0<3wQFom-tJ#|B0DC;#vckV=6!e7r;Pp2>VJAK?`hn+Y*^p# z7PU$1&%0M{6L#J<@1f zx9(8KRd>M|9|h;}&1i4mlYZE^mi^cgS)rxB_i6Vfcr0UUz9!?o|7jGD&+0=v)+EoE zQ_r(kdDF$Z5}7^I_H=KQbT`o1%+`EK%TQ-8m$LCH!;-a9wc(*jE^eZ~&i`h(#_aUK z@V}^cTiGuLhW}qYT^vJvPFQpujGUivU{(~*!k$xhjTbBDC%F9M3r5Hwu=ok zs`;I=@)DAC?=D`VRuR=drTX7o9hzG%-fPPtpl{(J5EzPKXvb>~gsJbi`w#c5Go1V`_w&^Znn8F`W~)7py|$< zDkyy8^~zqAE!>q^5l^o!@bwdY^JnwI7a~sozs={~t-3pL+q!?{FXx^7)#tOT{~inL z%liJKRSeY?WwdI=`xeG?ix47 zt+9yb-(8~{b@qoqn*UL!3L7S~q@};TJa?rF{@$y3*LtJZ_siQIE0p-0es>!5R|syq zIdwzI-=O%UrN6f*8_4fva@u`zgUl`lz4PxIcwaYfHT3$P=2~(t(saWo?UoM(7HjT? zFBYBr?&i)-?94~J{#@MMfDxy7;$S{n^i1R4(;@byxeIvF5(}TP|}%)$*->zq2R*yxGEq7a8{6tB`X0 zQpLRQ&&0a=Cx8Cz_NcG6PZpcw_;ot(G1~@9{rUg*I@K1l{!ZKZfAV`Nm(Ka2yUgFV zdw#c+k83(tQKNRZ+<({BvwNk!F|_@jw0XK-%(Z&$xp&uIu(5J{nK~y*=G)Y*L5bxuLq7i4q|%s;)jTRSA8k7)9Q7KdmdTe_-WIxF2rS6^plUr zd-ahCTT3Oje6wI%mT6c}s>-%xwSh%2>-{@>D~^<_$$gs5{k>z1a?}NX@t~&U@G^^+2iX%^$OsiU+F1Y(bq(bkuQr8b>&R%md(dE|Z z4KJ+uc3ZW68Im^EG&rHz&UNrrP_mqGoH?CaPZ!p_HtjgjY$9T1BP!>cRUGmjy&U1 zmON|e?>kakPOfWNn=dB9&+05;!Cux~-gRkj)4O&ff9B0eymLN<6m(Ra)wpZVK3!GR z?d86CzgF$lyeoe7P?fXpno0MX9<5B_uw8yI@s;(4<(8?_{NHlVC^`Rab+hi*yIH@) zX4pz{KRlUnFsmBG?zVIce!KgyW44(ce0B(S6J|Di+h9)asrl&k_lzabLGEeAf`L;Lae?@Ah%|Cj{Trc7F#?^~j>c1X48*Kf1 z^}M?Hd7`)PU1DzyU2^_b(Zw4=KX&|^wPW7a)Z2EK?$ww*?A<&4{feUVpL`B^uBrMR z9+N+%?`MCouY|?tFS5sXU%zDim8b2j-QU~x{-$+G^KRr`_>wsF{okJ8uERTLKS13) z!Ov6_zrgJHjTTVjx?TvfWHf#ryTbV$_x`2Yw6s?_&)6mHG+X`MonuB1D}V0$*W(kR zQGe!IY=+Nn1sls>=W_4fdHQ=7zw-CZ*;2dYAT56j8AJ`ORClEj5p5Wyf~?3z4*}#{i^q+ zvm?%K`@LzK)~n{dX+_&kG1#m5tuFZ>dfQvbUU+5XjFk7TZ)a-gdHuMew&9i4p7NUZ z!uNGeobT&7|6j@%*!11RiSK{Yi&?Kd*6eJYxy5Ip8Q;9m7fdT$Tn`VI7yh&pC(PAB_#`PKx!M1NHPxE6Q9&+18}|US*K9R?0a%H0jJN z<%+UfYBRphVfn9`e^C1~hugZfOFw29_H5&itYta8d6l8YZmGT3We#tUk$T+ITai?w z#HKEq)^|kib5B6CN_OZd{r&4l-+t$@dPGh!tFgva> zGc{ZwI?=(!QXr7cW$LSm8y>SQIxRKvhv0-y*X%#eaMzkQYa;7^X7z{B{@-6(2)God zaGm80v%KH`$n*1&_FvycweMd3m_6%(pWxE!E(`yj&#k?_bJVxQ)o?iPd44J8XRTc4 z=eBDbix*ZtR@}y_^zz1HGubBJ`x^z$WW3YLQM55+()%3O96Hf!O~U#U$xJo2IiIg& z7_VnZ^FQufVZl-+-2GrqtZHas`X@`*gDsU&QJc1{0WA`@ZaXGc*7>X{$=|l_T!jtqn-??myzez{>KD(6Dsj2KeBQA) zt=@s3@2yq~`WJ%JD*Z%0 zKK(Ug<3pRey*g8O+1JLkFEF|G&Cl_Foag+NZ}es7?1>7j{c8M8T;PbXapN;T<MW>ER$%aJy1d%YrmV!)D+kM~af zJD29f~+1@_BIf`9AOFkmCVY}C=l=s_TXP3;M z-v3Sb`Qp!0L@SJHEUT8UbKL*)sKnN1vK3-el%GuPH%z_jccZV*dHWBYA1Pr6)Sold zaAiFR4C%kPvs7YMWH;l@`3VJjtEJ*Q%i@YJsHUjixm2Fy^GlE?I=Xq^>Z}Xtp}KBj zb^3bkX&HuDZw-GulXF|%dHUkLY_mIId533gbvH1H?f$2|<;C_rT+NrY3@3Gd*XB!n zb87F4$)C8U{B?e~^^a{&>_+Lu8NX-v=(eq~Yg@RQFZo53C|m0Fhc~}u^_*FG@Ij{j zgO!DW5|ct260`Y|19nL@U%xb?*v@_b7Au}~rhm7zq`XD{Wa_-TygAX_f+N}N+J(}k z*>2wtFOCwrxwKt=){@_c-?6Suub+8fcUNruf!M=Ks%_i!s+rB0mmRQ9mJcesBe3-M zj3R|~EFHR66|2&jmC8&MGsO+w-mQPYxj&?2?$p)=f9((K|D|Qq|BvlI^L~XN2P#{_ z;#>{R&UDxn-}u#mLALpCDa(DYviY-L$h|&ddH7j}b@ZigoLZUd3?%peWaQIydpPf` zsaamsrkxjUw%=%$5j@^_arWv2>l>5*`R4il=iAzH;br6wpLqV&Ork7dNAE=_-kTM= zzJ49kv-2vQq7QZVUVeB)>#Xt1d7f`XS|dE;xVvk3Z#`R~^77wBg9x7a{L@wC4=&me zX}7+u=~vmgwQa#Cx&!RQSdFhAEr|O&QRA*Q*NhA6mZsW@wEFB?-+Gw4w7{!|InDp5 z^NU#w$1~p_n%B2bXOUgx{z@qg?yUu%VEWDrWQ}GUflNhiu~js zz0bG!d7sYO5)saR`p0&KlFsG}WkNZR%nw?v-`TWZO6+yk!z~5uXR3T!FQ{=`dp!Mc zD(l?tj}IBt{%)?*{k%0}ZYZbsd>)Q|*8`dc;%hJUU;4W6^8&k;B(;y1EiaVj?#&QB z^Q+q7Zat@e}8ULJz;_7_ru=zUzM~;Kk~fG zc6PQ?ltyoG=i!~ViEM4CJbrfXU+W%`_g4jKuARtFetAQWCU2-% z?pP47v_H3EQ_cMEnRrVIpDDVQG3^JH-45NkCf2l5cHXBBhZo<@8wMsfSg-bo^UhqC zcfKWW@sFC-CHW2POCxuEU-CDiwMfmsS}wcEKKryv^|rokm7A_hMTNb75W9GBt;QDL zJK<}as;++S*n=fCU%DbXkH09bzW*}gsU@$j&GN-_@8@j! zJd<(ZZu6*JYL8|9rhrJF|E^uP-{jNs8y(KlWcst|mD|nYOLBWSRfRIYWGh#9Vc~rsyb5x3#I0 z>SWp0*)`T_n@Svc@{`BZZM|jJ#nayxe7|x%!A^%SZ}s-XC!g+0Y8WBXq;^ z>&)Tdi{#(lSNHAR_*05!_CKb1GU9CuHCrX7mAOoMm#O2t$=>z8i7wmPz>6P4B>awl z&|Y)w#S@boyxc9XqrZMw{`%Q!#ycDC)AMcqf@N>QU9zMEeU9@7U9`}We&~HX z<#XFv(R|(C+FU_Lvc6yOTa%Hmboc+i3GCU)InRBQzb{UWkTrPqHtPo8*Qs)o!Xun2 zRSM7Qfl$AI)xYz7+*?~VVRXB40B);6obx!hlLRg4?=NA#Rsoj@q8`<81 zcA?n|adkiZd?D#%T$F!qx9ZJUNAKr%q_%L>N}f8tctJ@#-*MAw)t7ejLl>tT*}jfm z)%X63lWfeSdo4eOHww(_XmUQ4cH>!yZCSE3_08)^CHM6tzIo10j9t*?wC2?MM%CWSFL%B*5SBNa z!J_?rYC+Ui_GfPnGFpWfzw$r16!8OdrRn+>8m5^tLOFn74*LU=W2zP!`%&(e01-y$c~&fh(=)=L@W9hAIz>c{r>xs&hw+OK_g zl3(JnHtyvwtNtX}%#B{~(`o<0he>msWX|2?AZp~n#-Z}o*KhhymsaRZKJ+hT&z-Vq z$J-r0oS*jo`+e1hZEg!R({y zANO{aoM)+C*{kARZ6tVK;@drU<n~l*u3FFf=he)DyDaBtWna0@yOOD@S1xas zTSaE+hvY|Fz4}k8H#8niE%?^a6#wh@jE#S-kL>v_+7!34G5ww$Py9a7zt8t}uH$U2 zNI6wxV^=6B_i=tVUvl!rDi@Ypy*&=kKYo1i%9{D_GMytAUR*8kJ=qxkN@~md$9;{B zmmeCZ%l-+^Yt4V=Sn(vw!0x|s#r`k0$HeO!4R1H244_WAZK*>gUqf}h>X+xvO z6Cy0@FWh=uVdmTW?`0@x3(v*u)dps7lWivd;SzBcdN41p@#1R!q>3n0(Dpimy=;ds zg-C3PYTfs8YeiYF*yB&xN4>uH_i`TIaHUjY*Drys$#1Vp%&C<5o3X!cbzg! z2g;pw*;LjvoL%8raQl?T-sW?y!xs4_1pcnR}}6^d&Y_X-_-ER>Q5*y z$NGJI=cn5>J-raX6Y)VUNUP4V##K4#6i

eP3QD~gy8y{!Esr;zu0eMQ-`+b?GS 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%0D(Tg?&!{`3i=JKHGkGPb5@cXtNEU{8;d8Xx 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 z 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 U>+)FxndcxOPgg&ebxsLQ050Hw5C8xG 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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�cCgb#m`a|c{XyiMM^|2OP(X?bxKl6T=|ov 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 JgTXZr4*< \(.*\)$'` + 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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≠1#=e__gQwX=I>U$~RAL}$@- zug6oK7W|uD@N(YahXocgeC=@R&cV0wQnT7Vv!CGHwQQnr&HwWgC%?OG!@2M@$GqK! zhlLbbp53cFZxepv{{8*RcA4dU=kHVKYM9)*x!GAp1<@v<>FSkRje*uG~?aMg@!9{-k9}Z`LeI3z8?}6Sqkqv`t^+> zlUmp7hjDA&SBMqd{-Y_wG;M)-&BBEu+HwlYf0$__prNddYzLysrmXz6CJF8tX?rcl^F9a?SVD z>l>f3)nu;=tT(aS=>KXq=lA{I-`_AYn5peze{=7T_@%uT$G3c$bxhFZG0%dJm6r++ z_n9ZXIo7!&^WlJ@dmMDD#s3#4?5S0@Nh;eNO=V8(8}=Fgk@6XwQqtFAUX@AAtjOmIKf z+UswcZ?13+u#w@q{b73IeCJv*gNJiHzm#6FUo8^v{`=a3*lUUr)oWTlWU%P`{rqr7 z*_30Rwa+$peU=K`vwh*mT{^28BKN-(u<~guRoHrZUDM18pT!^iAE$-BX+4y6LsYh% zb@IF2%N;VFazCAtbR(#`Snb5-c9yRG!xPuJuAZ5H@XbmM4q4|!(@fdkU`<#D*aEY(7`j3C}mDTU^ zcQpxolRAG_>iyO7d&d8Le*XSle#HFmxsR24)9t74*37wg-!4z@ZZRa6{&`yP^4~kh zyg$n(3LCvYUh``+U-#wD4<5X5`Y)T$x?0ZJ@ZD9RmeuvH3swo%xxKs>n|**~amo3V zyue-gVvGJC^ZH(Ay5^JR@2xjA#z813G_OVCbA_D-mk z%QYt9xa)_`y|4T-Ya1VPc-VuRSKAGYe0vvm_b0y43Ol-w&3R_Y{x=eBDcOdK&y!2O zc{!c`5GpabjHCH_hJl4|v*PO2h83ZrY|B?0R;-mee7XOSZL+-8j^&ROx2yaRlXIRQ z>Yg#1>+rRp=qzIe-&V%e+L@8Pfiu5yXs>fBzyC5r+C==_?fr+AM9Ie9c9XJ?jW3HU z;rnN5wM)MD!&24Bum7%^!LeTaX^r_?-ioYQDu!`W?!UZu;rGX_<_2-z&(AEW+~xKt zo_Rs_>rm&vD-A|IBhw1i@n>4cZ%!RNq^2Q`F$wur5)p`#deJ z-{)Ox=(+!eaMZPf(>%XR${#+Ktl6TzC{g~CP|T&i&PBT`?PMp1uG3$!xG?KtQX%UZ zwle}xYkq6KelRJTxj1lFIzPvTx|_``K6}=;)qdoRIH%jbDEia;OM88{aiuLP@qc?w zw!OAaG-la8w$18?6>Wr=UvCIc^t~;jzu|LO_o4l+>!i$%e%%nUPu%G{3x~hkp}IKD z-p*eq(vL6ste3zqkdYV{!#TUf)FAt}hR&I_7x$k}*t0WN-o!p`>R;Jneb;xJ511V5 z@2~GXm>}W*(=}*r{?pI$QgsqGufOEiz7zhq&;OO?mw)A+6Q`}=`Yro=WA3pTdA}FW zcvoJ%zx+sv&VQvGolU>*IVT>f&AE3zJ~sc#>2ubz`mavuJEnQ+;U7_AE3wLV_jov( z4N?!!eI5J!G^qN@>wYJ5e@Rm1%D;L1)8AP_s=C><;dP%if`EO&+J(EHUrXPCp z#ZybxDYTbkW!4dwEa3p=tmTV~clI54rTt|eMwn}j>Jf*p7VG5-EEdHN>5>HTPo}J|4S5)$9nhu zQ?I_*zlrN__-aFy&2o!3n-;`w7SBDV_BiqTN)C;6j`u~^?5Z_wTf6e$hpkaOORqPF z$sRs1RjViUQtdxOo3|6Y{0?$IjQ^(Fw6!F!WL{io$+}PnhPkWlS8K#C7G8bd_~P8o zFL_zt99ZUh=Q7{Pe0h|mqO4xSXIJ@UvyR($Z<;1u?_RsyZtoSV9~t==7eMyT7YbN0g-(XP2SzEuUPg?EI$H{Ebzy_@!}H0deK z4`)i;s93)6YS-TlC)YPiPCg&G-!*IFrP|X^trN{k0iw`+0AVZzzlAdbl#e!hH|h^e^J6|*ZJLNm;S28!@K-lUdFwcl@jzaE_F?bzUTL{%Bcl@b&{vl4=t#$ z;oBCt{%}|5!T9sXPA;j|UGwRM)W)vw=Y!+VA8~pi#{RsuB=3#Qr7gDBo)!)NKrpFz!I`zrC z-ZkQxWlzmJ9yTBQTL13vw!wafb3e}9Y(FR4Dt%7Mc8AX&RF>{8%qp^|7meBV9JQ5M zU{PN1dwSclhkqE0n$~4LVY&amct=Tn{?nNs1s>V$DE>Ra0#pyCL_C`NxVw_w$-LkC z`u(@?DiytLIbFQb)OG!#n=4hP`0UER%=-6IoKwbAeU79>R>E^uD{$TR*^|z+MSpcl z-jo&p^IpcS*>_HB{^cJLu0Ir{ygjapw!Hh#c~O48`;X)Ml^&fCbqR}gp7P2lX|=x6 zzGqsGR_7=5Z4-PaZP&KAs{BS)y&5>aK?*ADN}a6IUqE942#gM^9AC6PaVek0*bO_))|X_NU{dl-gb9 zxHYqE=PnP~J1_K$`GMYdtrwPF>!+z-*0d_IvfPrRhm~w%pr2 z$+kh^Zg76q>y>-G{>Hj2cxH8|FV?N#Ww^wo=(dT{-;}>Rp3D*U-|O=OttPF~4+~$v zaeSP3{QH#`50>UFsVm*i^Iv#Ibq&7iL}ra4_985?)VoO0Zp zc(Gi}>YQHV<@c9tt;^AlSd2AEi2sg`NZ|EOS^+=CLGYXyZ%3mSn(+Zo5>np-_s7hPPndfcXQe6 zS4X|RpYzmr{#l{Xay%(f_^Z{LnI-%CY%>n5dUO4dR=D%^6>@2xSFV5eQJ_loVC6-> z8RC~LFITDE)&BJQXm(a`4D0L}Qv2DSPk=eJu7r(s6(tI!=<;%SMqX|3q z?E7=+x$Pu>({D4Y^1HspgzY{WG(!UgtOG-hT@o)%Gu${2}t6@IITH8t;yG=>LdZ6PACgkMm1T&E#uy z4qLP9J-!s$yz%SYId zl=44&vtfC9-T~v!_guCgeZTPcDgF!AwcKp!`GHH3O_!kdy@tivJZ)!A zv}U_Wwk>;q_=b+!QTf9YOs7gr3+y$N%y@4s|*rCRoO0GG7V?GK2=UM|7JR>dm)b(_FH$UTELt7Z?gZQ1Y-W99svCK<;$z&3$kyMp6ZvH~ zygn$`k-)F?WAmvWQx<61F6Cs@+N<&K_a&<^-T8N&)$Z${%C_$oWSmNIzv)p^7F=WLB8Z zUt=F;xj%B(`h}0L94P#|++cnv*Ta=89Bcn32>xnPxx4vsu(g?(`>g77ochi`GdNn@ z7bVDU<=p1;Nq^ae9cNzDoGHFipl8MS+I9DW7ZW)arVGi4g&gQPmJ(JHv#amX>!=8E zndaOWDG^z@_SRP3HXfPAa&126;z{1$&oXQDN;Su4M*rU!og$@}=GM@+uKgEy>mHE#a@G@Gw?*X&KdJM*QFy!z(Tkv_?$x^~Vr z!&xh0uTAT&(E7LJ&Bs}nm4B){+gB5Jr~TlQ>adspzG;2kn^Dtk9(%x3s$H}9%eOzO zwskvQlCyVNZYvfq_0aeeStD^^%lzH8dynp)Qt<6pHT$|hll>lVDls!(xbyf7cm1iy zG*3SKGZnS*JBPi|?%n;GKkJin4m~WeSa#>%(uJ88^$l|tWJ_5d{Qh4?@9zC-&%2xl z*BxE^`j(Aief|HwEw#U{ZM9Ee(4^nM=qW8W@k<+ukGxY0N zxy<&p_sPn&?VK67D?Y4e!(#iyMN#bXYQe!aSAtqUr*Ym2+4Vnm%|Gc!!E2l6YxJ8R zioV^T`Kr63=4rG*o7;lk_r)2~TbS0Wt!=tI!kdr08ahc8|{k)<5X;qr$sl!)Hp+9I((i+v7T>tc1;%l6xam}eI3_iA-K z-Jf(ps%ma%^S;&mi_32r@|?Hj`@hHe#gl10>XSD=*#BFFY1PYJn^t}D$y)yU{ox~( zW&5_=^G}`NQqCp(@n*K?7V{48z&*=Z!i-+V-Eyx=aC&um^D@r3TWUSw<_GtOwR^JH zxokhG?{v{z;Ji;%3*+VMi9JHDdFIf-%lr%tk0PCj$8HRK2_l-)oRW^eVFD1-D~BY{OC$1;(vQ z9~*zXUw2pCVE#_oLz6!W++sOk^vBg|z1EEGB)hM%A?(X-MCT-#Iozz|n(J?plXSmj zt;Unhbs2kpX3cnh>8qw40qO`QLKM(tX+b<6v}8=Jho z=gKuMU8;Mm_v3+Wf3q_DV<#hbFaPWFupJf_`L%M#bN2*zomywjrCi#Px`XX z+5B78mp|H-2lkhyPB%zCTzTZlzn&9s=1&axuvz1Tc+UQoQ|I#+JUcBdku~LAr2To7 zIY+v!>zePq*e7pMD9fDYQG376XIfg|{xz>wZDF3c_vt0sw^eHM4;8PEJ23UN`O;tS z&zn9xI(K@ejrfF!58V;6_2;~wPyGB)Rp#Flt9`#$a(1@vzf*jvcKwvgzM1c1esCFF zQ2#Oa!k+4xpy8qu5C2?6&&Z5B^#f)n3hid$dLeGSY% zdH?Z#ql@|XE!U~PlyQpNRs8qy;iu<+=5gWd+*rH6!^PM>?ReTTrp zc7Nr7yDDCHeU_z(p0T|;DdoIM#x}n-)6TWL*dMs-`(l=i#*eF{P4;DTg#Pb3#~s%+ zHQVUV^&D4+Uw#$oZ@oGl&9_Z#p4qb7I$t(0FqCgrp7!V?W1NKKPL6fnb_Wiy-Zrc# zWIeX#R`*5i?+IsSHA_scw8lRtc5j@6FmbvR`;bjLQEAq zPJWp>DM7#MOUe8Q(H{+72l}T4rB-?d@UKYv-X1zX{kwnEd&j>OS51?eq-KO%Ww<~6 z`{BeFDO;q(`&Ufof4H+Y{o&-j(r@pkEzGTua;y7c`Ddofd98bw?SwOI);zMa=CfXB z!M96%&8fdExx#lea^gIK{?+T>t-j{LWdHj#x6h;Rt1_LA6w2PYXT^4$@tuIq8^wt2 z&CStFQEP7TolY`svEe=A@-j}_>wD7D-|0yUUx=_h@3@{Y{p6DB#WMO{-+e-^A28a_ zrn^}?QFwxG*skp*v0T%iRwsJCo$_;1gI+*=S?kpd!*uN_waX5z`ChVR#!tmJD|#bd z&O7;~bxo6<@Q2rl!AlxXFEWbwGS{!x?SN&Ou*~(adsE)cRPq0P>uKi2tUAdvzm_;v zWN@uf6FZ=|dgrCRDwnu_PAXZivB*$le#6f%YX#dNLl}nzhC?;S@(er%zI|`Myg8p_seNq->1gkarkhnILTV=SGj3l@7}c27eAj@XxZi; zQ4%II`K96eXB%D%PW=1%`*rEB`z8Fpie>Dp^?8{vr%C4AeMWe`1P|_33QYLTwAJ#M znhali@~O8d(=e6qF02Z6%K6=XZ9_`+L4} zr`@~BC;q;f)wYfiG7H1!&Zc+wK2j~N&lD+r&w+com_cxqQ^A{td&ZXq|9s@SLP*iJ`2gz&U4PV7uO=bQXnFPcfI(P4|O}m9HspePG!$eKDA4@BRam*aAjmg zMGniI^ry-hXTy3T|Jb?N{x@Sgf3u^`*nCm;CGH9v8|i+w<%@+^&$3ry)4S{TxHB;L z!qt2;xv%ru-S<3yymEnRMb#&+65jJiE(h+EF#4R@Gdr~BTJ5pqkRLier*3hcZ_4U9 zZ_^W)$!=)#m!mCJ_V9%_$uB0CNhovoA2xojyhTpFIX&%Z;-|*V99K*ZIiFf- z4_6FU?rl6bJE)YIah2P~Pqvd^-rahsS@4Y_XIcJ@c(sHgo5^o(nr2OVw>MMv_iI)= zQ}YL_ce2U%%1!?EzbB7#b;9-2vdPo(+=6aqE~zUKz5G3<#s6R2mM=5g-5(bjto#zq z5^fq0Z{;I!T9=QPV{w`d*)0OAB-^9ZEeX2AT@}SYQ#O z6xWdiKcDsC!-R;wXf4uk~t%p?y_AEGh*Yd7E!dJG*ZF{{XW8(&%Y3Kc#FQ`iO73Lo>T}SaZ$Djq^l@g%`vY2c&);AJHQ8m= z4{N; zGC}I`@#FO^2NOJ&-O;&RCw%wdWAj(P{A{mYNPm?XdGJ@B*Y_opIZoTO2k$rOZ2tc( zN4RSFyg$=LU;PfKT59m~pWn=RosZq^_9}imtfBSSSLEAGyY1Uk|EBM&KY2swMr1MX z;b(L9xlTzJvR!@Pe$3;4uZ?|Y%&Fsx6@GGDz1O#D!h>(Q<;7J?=NxNkO+Pn({|ftD zo2$_aD!-XNjeLCNalt>m9VO?HCRxulH{20lan{fbBo^%UcXq))22025CPhv9=WAt* zPe<=>sjrb}xiWYAy5!8?TmJm3PrX&Xw>akHQvo^2is!$U1pOqK$?I8-%=a`-(~UZG z=nhwtZ}u0Dh%Qa}+Gd8*#VU7`0iib{cz_q1GjZ{U3D+rKjx&VvOT|+;8JJnmX;wn zBfPVblQqNGOmyDqd+rGnxz_Ex`XMHqeLe5j4|OlSEB1dB6Iz`zoqhtgBpg z+}}&CUzmQ%Tf*RTDbKvUlFZNdHc!;{GR%4E_hU`Cvc_f+{$5_k|2J=TSk|x!milsB zdSy8A-0A*xl?u9lvu8huzs_<0mNdr|lMAIg`U-EU^{h4SxcdC=%PNDP_vXfJ{ywkd z@1G;T-LLnolb?JoGV~Y2u6d1D!wn-ZcNFG_9j-g#V)IO}f5!Ix4tM6MwM;cttnz1` zWWTRtqxXHs+kzh@_GK6=eV^|>VHc;|R-Z@d{MH^*-!W_7jJLmVxcS(jCDr9?{ib|4 z+OG7E<>frvgo`!jZ#GNBnOe16H&48??C^&7_Hn=R4xW+RrhNE9)zz>Fv2f=Tmz`#Y z^SdV6%5a$$%=#%eVhMkN+@#I6*OVm$+(`mD=QYZ!`XfZQg!xO*NBxqi^P3d9Uv&5!Tz= z=l1HVRj?(je>Q)G^<@Q=y55kjS+W1po0k84YqM@`czj8HZ_vBCkMlggFO+=#;qm!N zCG{V>jTOOO+h?eJbeyyJpw=&(J;@r&B^K0+_;Gbm~bn}1oPLIzg zuUkB|n6qCPJ|PPp*Z!=a@SQcQxW^Y%kAGHIRDUtc&F{n0o&A?LUVj^Z$hqSN%fpL~ zEnsnwZD3T^#05z zEoXik&Y#PEZSPiI*<0let*xpotEasX+|F^Z`XA^2S!@+y-cyf8npUdi7(blXd8a;h zY4v5T?jzU~d*2zpPBbr*bJNp1`r=En#EwrKZ0qb=7D~EbNWZDrb7JMu z8BDxtB3Bh&ERFJs*v9(S{n&x@n`tcT*B?7lx|KsCBsnP2^p6pL!k<^M5?i;h9Dbhb zGJnOGu0GBAV|s1#g1Podr|!uM7X46X35`9Fa@MrvYRus$vsQn&V3x3E zTIdY(KXv(gGH%Aaoag!NU#QQH=_(b0yX?~(4DN1!aLT*l%}?>fd*`_yFMFn3`MUDR zJ0_>oyc{m+A(tFy&vCo%vBHc<#{jECRVPxm zM19+*a`*Xl$C>ppF}K85Tul1LZtz-+ePJ@kn`Jo-wa*MSTpj1Na(?#-nZD$=S>@FY zyk}ZJWU#C`rX#qtnmg;T*4^3NqR&@9^eFVK6hF0GDB)hcSd?A3(#bbT^P@B(rq<5N zUz0yud`m%Hp0!TZGN%eHzH^@W-Iw-mirl4t#X)0Lv#(xn@iVa*w!+*GKNj3@+sE;H zj+W;x{b@_8xAiYt(Dp6(VBKBLm6!IeESVq7>Alg&cB`%x-|;VtH++oZUg)c!Q+2-O z^c$my&2}>i^xb&5XB}Rf@n)K$evrMQ0NZ)jZqeU~)BOK#*e{{*ZTYgg(1J6!*uC$+ z47g>{eZR4S-RI!L^L`8elenz!s(R|ir6YXsO74xt)%ckxQ?AzBW-P^XBJI?xl z?)D>-t1NH+%HtG%blkRHZ0h{`YXbH!{w1ZfsqWZ>$mJhP?s(b=*9+NB@?Ug*|BAZ% zGTUme6fZA)x9CZq`KyK_hvHo}-rpiS`Ez9HU$?nm_8hYJGgT`1>c4$U^7_Zzb<6gx z5N`S^IrX=3)tzhC_s+SuGCia6mzU?$sroNO)_1CBF`d;tR($N`v4?-|;^~FYvFC(W z({nBw=lvIwb9(Q_pz>a>#h0@UGjeU&)~r1`;Y<|IS)0DZE$6g%v&Am%diY|SL~{7MqZ(P(D!#3b ztND{d%1nQ)wP-r{@~XsyG7e>9{=>H4Iez(8i>`H3NjP(f-_U5gTyydMBMaV~H&FY} zy+gE??fcjHhimT^`qX$b&)>rT;7YcMjqr2jT^nT9X>UK2`?Fob`gv%NziwM}5hI)X z(uXS-xK>=LW}GJ>*K^MQo7-{6buV5_?%_LlesS^@Et@GbnvXAAe|&QBwS~7IgkpTuC0RXKKZ&IxtaIL7{rHdfBp-yYZO-=<-V^Hk zVI`B(i(P*AKI*>tn|`Rw!yu`fFDWWhIq6ix2`+^-G3^^RvFY3>=Ur&I`@thC(OU7n zjhfZ|N)FY`c|Na4KCeW6M#1})(^kFO{J5)r=7arnmEB(6n;m+6d7bU* zFCA;HC;yr4{=guiN#!p0w+(Mzb5D28KfGy0)EblZXY3i(u5R}FzIOg(trOh*t&&TB zFFxk=J!r-Sy(ifTp$pu0?~%H}`qZ&Ph3A>F*rCjyaWlk&X8*C;_-JoHgtFInKhD_; zYmDTctugx36cm`2UbM#{cWUd`pryYTRo&PZ!T#J*{?H=n$?t?8%-AB?H#0P7X|>h7 z16tQF+ilyKwPoI3(X_i6bNXF7F71__{BGtKy|CHWFG!eNvio_pAm+QKPTCZQiZ;*h z!7eZ7Z53ZyoppRk_3|%^Hv<(hr2SHd?+J##h9n7O>|>5(Lk zxz34pS%Oh(tyqe`IWVohcmCJn&s(*E4ko^pi`b#&koc6NxK&8lIl17?>SBw3!IR0LPFToYb?^BcY4`lDw?+$(I`#n+gt-VFf(Hl&U zvcqoP+rqng%lZ0@8HnKDNT%SGD+2d|$y8QP@NM3J(<#lH8 z>c@ADkSctQ)z@W{{#LxXcswD|@Bh4wC2rd<+?=uLvGi><0&bn?}3!Cx1@7jQ(jwL4!nNQ~4} zGia94g`O@TAr4Qej_HhBNCv7z;~z|T!m`}FJB{)^kYUiekjB63~vO>31;-1^4qYl1tL z${#y%gsDXGMf1A2&KGjC`;IT({Gq^w&5C8cZ$Bp(qEbL(~ z=XpMF4Kl6Tc&T=uRM=0`jOo$5zYoM{ShXL@xxr+i^P3~BqX6$s()rL>*lK5-F|9a$T z7_>Cs@&DPG%j*3iO1@p6r&+$f;=Ic@M`QoR3Aa^avUh0SGcSI+OU`~yw$oK{^9ehj zD!HArRt`Hc{q^efPo6GM=QQ8DarR|=31}V47q5xiHYfMat7p3(T;Fs0`j?7#Q@N8& zYcJQ$TlPINT5qAJR!P5pUCy8B$M=X|R(`p}ZsCXLo3BfKdA`(mkLWD_)G21iG><*J z!%JjkKCjLw==TN{j>%o`W6m3W(_f=vpSSLhgqoO>giP)8UwLw${@zK4w9mOw+vmI4 zA*(QD{(H9js{a1)Bf0<3wQFom-tJ#|B0DC;#vckV=6!e7r;Pp2>VJAK?`hn+Y*^p# z7PU$1&%0M{6L#J<@1f zx9(8KRd>M|9|h;}&1i4mlYZE^mi^cgS)rxB_i6Vfcr0UUz9!?o|7jGD&+0=v)+EoE zQ_r(kdDF$Z5}7^I_H=KQbT`o1%+`EK%TQ-8m$LCH!;-a9wc(*jE^eZ~&i`h(#_aUK z@V}^cTiGuLhW}qYT^vJvPFQpujGUivU{(~*!k$xhjTbBDC%F9M3r5Hwu=ok zs`;I=@)DAC?=D`VRuR=drTX7o9hzG%-fPPtpl{(J5EzPKXvb>~gsJbi`w#c5Go1V`_w&^Znn8F`W~)7py|$< zDkyy8^~zqAE!>q^5l^o!@bwdY^JnwI7a~sozs={~t-3pL+q!?{FXx^7)#tOT{~inL z%liJKRSeY?WwdI=`xeG?ix47 zt+9yb-(8~{b@qoqn*UL!3L7S~q@};TJa?rF{@$y3*LtJZ_siQIE0p-0es>!5R|syq zIdwzI-=O%UrN6f*8_4fva@u`zgUl`lz4PxIcwaYfHT3$P=2~(t(saWo?UoM(7HjT? zFBYBr?&i)-?94~J{#@MMfDxy7;$S{n^i1R4(;@byxeIvF5(}TP|}%)$*->zq2R*yxGEq7a8{6tB`X0 zQpLRQ&&0a=Cx8Cz_NcG6PZpcw_;ot(G1~@9{rUg*I@K1l{!ZKZfAV`Nm(Ka2yUgFV zdw#c+k83(tQKNRZ+<({BvwNk!F|_@jw0XK-%(Z&$xp&uIu(5J{nK~y*=G)Y*L5bxuLq7i4q|%s;)jTRSA8k7)9Q7KdmdTe_-WIxF2rS6^plUr zd-ahCTT3Oje6wI%mT6c}s>-%xwSh%2>-{@>D~^<_$$gs5{k>z1a?}NX@t~&U@G^^+2iX%^$OsiU+F1Y(bq(bkuQr8b>&R%md(dE|Z z4KJ+uc3ZW68Im^EG&rHz&UNrrP_mqGoH?CaPZ!p_HtjgjY$9T1BP!>cRUGmjy&U1 zmON|e?>kakPOfWNn=dB9&+05;!Cux~-gRkj)4O&ff9B0eymLN<6m(Ra)wpZVK3!GR z?d86CzgF$lyeoe7P?fXpno0MX9<5B_uw8yI@s;(4<(8?_{NHlVC^`Rab+hi*yIH@) zX4pz{KRlUnFsmBG?zVIce!KgyW44(ce0B(S6J|Di+h9)asrl&k_lzabLGEeAf`L;Lae?@Ah%|Cj{Trc7F#?^~j>c1X48*Kf1 z^}M?Hd7`)PU1DzyU2^_b(Zw4=KX&|^wPW7a)Z2EK?$ww*?A<&4{feUVpL`B^uBrMR z9+N+%?`MCouY|?tFS5sXU%zDim8b2j-QU~x{-$+G^KRr`_>wsF{okJ8uERTLKS13) z!Ov6_zrgJHjTTVjx?TvfWHf#ryTbV$_x`2Yw6s?_&)6mHG+X`MonuB1D}V0$*W(kR zQGe!IY=+Nn1sls>=W_4fdHQ=7zw-CZ*;2dYAT56j8AJ`ORClEj5p5Wyf~?3z4*}#{i^q+ zvm?%K`@LzK)~n{dX+_&kG1#m5tuFZ>dfQvbUU+5XjFk7TZ)a-gdHuMew&9i4p7NUZ z!uNGeobT&7|6j@%*!11RiSK{Yi&?Kd*6eJYxy5Ip8Q;9m7fdT$Tn`VI7yh&pC(PAB_#`PKx!M1NHPxE6Q9&+18}|US*K9R?0a%H0jJN z<%+UfYBRphVfn9`e^C1~hugZfOFw29_H5&itYta8d6l8YZmGT3We#tUk$T+ITai?w z#HKEq)^|kib5B6CN_OZd{r&4l-+t$@dPGh!tFgva> zGc{ZwI?=(!QXr7cW$LSm8y>SQIxRKvhv0-y*X%#eaMzkQYa;7^X7z{B{@-6(2)God zaGm80v%KH`$n*1&_FvycweMd3m_6%(pWxE!E(`yj&#k?_bJVxQ)o?iPd44J8XRTc4 z=eBDbix*ZtR@}y_^zz1HGubBJ`x^z$WW3YLQM55+()%3O96Hf!O~U#U$xJo2IiIg& z7_VnZ^FQufVZl-+-2GrqtZHas`X@`*gDsU&QJc1{0WA`@ZaXGc*7>X{$=|l_T!jtqn-??myzez{>KD(6Dsj2KeBQA) zt=@s3@2yq~`WJ%JD*Z%0 zKK(Ug<3pRey*g8O+1JLkFEF|G&Cl_Foag+NZ}es7?1>7j{c8M8T;PbXapN;T<MW>ER$%aJy1d%YrmV!)D+kM~af zJD29f~+1@_BIf`9AOFkmCVY}C=l=s_TXP3;M z-v3Sb`Qp!0L@SJHEUT8UbKL*)sKnN1vK3-el%GuPH%z_jccZV*dHWBYA1Pr6)Sold zaAiFR4C%kPvs7YMWH;l@`3VJjtEJ*Q%i@YJsHUjixm2Fy^GlE?I=Xq^>Z}Xtp}KBj zb^3bkX&HuDZw-GulXF|%dHUkLY_mIId533gbvH1H?f$2|<;C_rT+NrY3@3Gd*XB!n zb87F4$)C8U{B?e~^^a{&>_+Lu8NX-v=(eq~Yg@RQFZo53C|m0Fhc~}u^_*FG@Ij{j zgO!DW5|ct260`Y|19nL@U%xb?*v@_b7Au}~rhm7zq`XD{Wa_-TygAX_f+N}N+J(}k z*>2wtFOCwrxwKt=){@_c-?6Suub+8fcUNruf!M=Ks%_i!s+rB0mmRQ9mJcesBe3-M zj3R|~EFHR66|2&jmC8&MGsO+w-mQPYxj&?2?$p)=f9((K|D|Qq|BvlI^L~XN2P#{_ z;#>{R&UDxn-}u#mLALpCDa(DYviY-L$h|&ddH7j}b@ZigoLZUd3?%peWaQIydpPf` zsaamsrkxjUw%=%$5j@^_arWv2>l>5*`R4il=iAzH;br6wpLqV&Ork7dNAE=_-kTM= zzJ49kv-2vQq7QZVUVeB)>#Xt1d7f`XS|dE;xVvk3Z#`R~^77wBg9x7a{L@wC4=&me zX}7+u=~vmgwQa#Cx&!RQSdFhAEr|O&QRA*Q*NhA6mZsW@wEFB?-+Gw4w7{!|InDp5 z^NU#w$1~p_n%B2bXOUgx{z@qg?yUu%VEWDrWQ}GUflNhiu~js zz0bG!d7sYO5)saR`p0&KlFsG}WkNZR%nw?v-`TWZO6+yk!z~5uXR3T!FQ{=`dp!Mc zD(l?tj}IBt{%)?*{k%0}ZYZbsd>)Q|*8`dc;%hJUU;4W6^8&k;B(;y1EiaVj?#&QB z^Q+q7Zat@e}8ULJz;_7_ru=zUzM~;Kk~fG zc6PQ?ltyoG=i!~ViEM4CJbrfXU+W%`_g4jKuARtFetAQWCU2-% z?pP47v_H3EQ_cMEnRrVIpDDVQG3^JH-45NkCf2l5cHXBBhZo<@8wMsfSg-bo^UhqC zcfKWW@sFC-CHW2POCxuEU-CDiwMfmsS}wcEKKryv^|rokm7A_hMTNb75W9GBt;QDL zJK<}as;++S*n=fCU%DbXkH09bzW*}gsU@$j&GN-_@8@j! zJd<(ZZu6*JYL8|9rhrJF|E^uP-{jNs8y(KlWcst|mD|nYOLBWSRfRIYWGh#9Vc~rsyb5x3#I0 z>SWp0*)`T_n@Svc@{`BZZM|jJ#nayxe7|x%!A^%SZ}s-XC!g+0Y8WBXq;^ z>&)Tdi{#(lSNHAR_*05!_CKb1GU9CuHCrX7mAOoMm#O2t$=>z8i7wmPz>6P4B>awl z&|Y)w#S@boyxc9XqrZMw{`%Q!#ycDC)AMcqf@N>QU9zMEeU9@7U9`}We&~HX z<#XFv(R|(C+FU_Lvc6yOTa%Hmboc+i3GCU)InRBQzb{UWkTrPqHtPo8*Qs)o!Xun2 zRSM7Qfl$AI)xYz7+*?~VVRXB40B);6obx!hlLRg4?=NA#Rsoj@q8`<81 zcA?n|adkiZd?D#%T$F!qx9ZJUNAKr%q_%L>N}f8tctJ@#-*MAw)t7ejLl>tT*}jfm z)%X63lWfeSdo4eOHww(_XmUQ4cH>!yZCSE3_08)^CHM6tzIo10j9t*?wC2?MM%CWSFL%B*5SBNa z!J_?rYC+Ui_GfPnGFpWfzw$r16!8OdrRn+>8m5^tLOFn74*LU=W2zP!`%&(e01-y$c~&fh(=)=L@W9hAIz>c{r>xs&hw+OK_g zl3(JnHtyvwtNtX}%#B{~(`o<0he>msWX|2?AZp~n#-Z}o*KhhymsaRZKJ+hT&z-Vq z$J-r0oS*jo`+e1hZEg!R({y zANO{aoM)+C*{kARZ6tVK;@drU<n~l*u3FFf=he)DyDaBtWna0@yOOD@S1xas zTSaE+hvY|Fz4}k8H#8niE%?^a6#wh@jE#S-kL>v_+7!34G5ww$Py9a7zt8t}uH$U2 zNI6wxV^=6B_i=tVUvl!rDi@Ypy*&=kKYo1i%9{D_GMytAUR*8kJ=qxkN@~md$9;{B zmmeCZ%l-+^Yt4V=Sn(vw!0x|s#r`k0$HeO!4R1H244_WAZK*>gUqf}h>X+xvO z6Cy0@FWh=uVdmTW?`0@x3(v*u)dps7lWivd;SzBcdN41p@#1R!q>3n0(Dpimy=;ds zg-C3PYTfs8YeiYF*yB&xN4>uH_i`TIaHUjY*Drys$#1Vp%&C<5o3X!cbzg! z2g;pw*;LjvoL%8raQl?T-sW?y!xs4_1pcnR}}6^d&Y_X-_-ER>Q5*y z$NGJI=cn5>J-raX6Y)VUNUP4V##K4#6i

eP3QD~gy8y{!Esr;zu0eMQ-`+b?GS zQaYwv$Ibj~UsK%X_=CxuM_&l-m+SY7Y}OMNe?0Mk-i?ppEnhi8O;Q!e;!*|NGJ zVHSI1$^O0C(hp-@KQF7a3!IVifBlkbf2-}$ORHDvPJVZM&+4|n9~d~Ml?j|#v#?@^ zc$AO!v?bMn%hE4Z$gU}#S+YNPRmuHh@l(#vELpFUrc}GR#D6_^R$|-%t-I1of44U$ z+Lf$Rxog~bPvh?9Z%IpkpM1TrMn&e-NAJY>X=_T(FZpfb#VGtfQ~uTYm#tA2YI_`W zm5+LT;yamt(Cl`|uJx^viL!G(uX;FVkK`$Jr3DpQOnrsx4=cWBnbJD{&XjkNrFCY{ z4rT3FbJ^q4-(*99TYinPkD@o63wJ(vZuJVWu%lZy%>TU*}cR)4eKU%9-g>07*{ z+SzLT|C|#o?HB(q@eE6UJ?n5v&9;<9)$v*Xul>_L_L<>SX!!r7YwjEWRH+I3(^5Zg zn%33-Usm(!zx~7ey#8|cd;Qpp$yfet{vXzte!S;|UAy*m)x!B7-yPJJuzGG%bz$4> z<0lS%lXkk@e%`kBddGL$N#_+ZzV4h48o$i8>5X1c`AJfDo|&I4-}Agy@9&@t{t(nH zX+QkXhT}wS<1LwFVZQdm5C3>V&tIvOTc9%Cu^^-PxA^BEHJuiLKT0iPbDwTEKYokx zknv;1FMKTmAqB<$kxX=i>#Y&S2UtrmJvc`;o(s)s#Qz znyRfX{c!Qs!?4Q=Tz9v7e9sEgjTKs=8W<(C&{Z=^WU=Gzr3Rj}_I@`A+xq=7lk&It zrrR6Yeq`O)rTyVrOmlmi=VP`VS4|V0a+U8~_1&_MQ}L_si}_nw*V+gf>@1K^d$YH3 zNB@_OdyAbfy<=0E7V8ppvhv8j|H?}7|9K2&`zdVs!SUzv=?B05NK4F$YKfb;`oota z&p&ot_bHK>-_-Yb^2ZA^A}qdtb3Ogf@r8krSpSamM;6C%fABdGWh1?xPx;v%&g-u4 z50*bQYe{|Y5)sB}Rqc0p^Fwa~)zu<(tn3eW$8oQJ^jKp0>O(KiG)v5`vQ&Wj4VZHw(zb3_S_b-lr#b#q-#dSDs zU(dsr?nk}8>o{-fPYwvP{k6`jt1-JT(P0-?a zPONr2am%(~?q{Q2Zo2NX8*XmkozdMs`Ln^ZP*EqoSf>rEI2(^k2?n@+Zu_gsEZRHy z*wf24`8}YsV|tRmE!cUOr8e+V`tw7#R(#=D&3Ix2odP;s}6QM)#~Pa%Dzx{P;SO|kJbxj49|qa4s@>DI4N|O_|hU>kvCfdcjX^mQoUbC z!^*Fz=z@-g{;lHRUHVJ*ahnz_G~3458c@R3Sac`JV$DB}Ypq!tGezdR*FKY8`ddj? z#ylfY`YYE~-sTT3CNe?yr%in~H$%=+eJ|_N#}5VMxEo`?tln^bZTq9|Ufm6Unur3T0So1ip-PLbuh1{B~{i1Vs|2@OMV!s5(TIQo%|M|4PADVOQ zq#T>UY4eHijQb8;N>*)gcX}W&pw`wH2woBM2cENI>Iz#`_wu5B{|c{aQMatgSy z$ocoTh)MH$SLN%S_?L%Rh&-Q*!MOS029GVP-ak3!ZC(F+Vfm|-ZFcpWEAE>#tQEzb{&z z_;&ge&(~PD{rwVFS@~M$^DV1HYnPvVpDv|#X}$h$-W@x&wf665TyiMhpz+v=V`kG1 z#j&T)Ih?&)fAP21Y)8cZG`;gwPyPKz`*Qa?{n*3Y^Z#D>(OURpyF>94nWI-NaxLcU zmLzI5W95NIbIX2VSohK0j3RX|QwXaC>xXBIcVh*&D%w&X;9^_M>q{&)TkKzBSml=T z=w(Ce3mdD6=j^%Ovav1tx;gHJ$qo5fH`c|wqj{>TR1Yof7q{Y@-P9Q9ZKz->tt^&v zWGfmn^C*?l~)Kmgl)G-RbXHkIyT1UfQd2XY)>#wci6@y1cxXd|N%k zFinVsJDP(_n}h#rLvhx^ke79_#jX{)IiIe+*ij*`^Xp8*1v>$+>!}4X#VlTGm&6S% z_lt3T`&IUG-(eRH=ClJJf5vW^$N9@=*L1DB+ZUL9Yjh1L;cLEG@k;-#a)kJbg$o{g zUb3G2bmoe9_IB|t8@Os2yV?&ce(`Age1<<^_Toj!mkRSIzl)u8ujSLm0FHj2#?mXN zH!S{nXi4>ErfqvP?`n5tU6^z%QSz%)OS`h-7M_;UH7a+z-*Ua!b3OB+$#3UZ`fb}b zbNyfaV9GXmvDP;W8)jeH%epmp>o)_PyVIO2Ohmpp{><3gMHbG z8dJ8{JL*6ExG?BwRsU0gsPc*VpYJdE`uNIsBM#1J#ggxn@((}S=Ti5E-vKg60$O@y zfiawFDmrz3;+G2Zh=>jQ-z`0G`0!tYr+=$hLSH{zm(>!kEBr?F;l1bap}WkzE}5rh zMj1_AMXE`Ubcew!}79sTFf?CjhtKA zdajxs+#T0m@U@gDZky!XV=Br9f0b22?>LwUzw=fMOY*n}Yb`%6si}m$ zaFw)ARM#!PuxoCw$DPIQm)zM@-dt0h_RC|tFEm#Uo2u{|evZhaXSAm=z&%tFNo=^ojzD-oCcaD_FKj&+a*1 zleW102iGlqX1U_7m9<e)AaU;@8es4^u2PzSH2%h-#P|7bF4_0X>sr4s(YO+ z`0o}c&+NFKzQxLhMekNi%wK(I!`7=38(yu5kena7OMcSYqYrpQ+m^_>9yxaYW66o6 zE#GE#UFeb*h+6ABp(}L5^53r4qpBnpG43DamU-LBbT=JY8orFT|2s&>&F(` zqH5(wFG{bN?RrZc->_9fA_iIv1iUp z`Tm7gZFI7qtvt`9?pwlvx0h^%-yUVRo#p(xCS#q!OS>(7&$!nFEDT*{Pp)8DlJ+-$5^!qN2e$5XXSw!)`p{?SvuTfSsp{q%RQRbK8} z!NvtTnNe|rMAvbp#Ao}M{4%Q(XU6NEdefwFeDlAaOMk6&*JPa2wrY2NVa%)hJg@m~ z?xnq(Ht?O>c{Ssp*4=W>_D7us7Dx8-wf_;`@-}#eVdwl?hBM=;W-O4>?Y^?1=N`wy zi5VQZY6sOG`bIpx_@nE_$E_QdmYv(Y&EaQ-OiP(?;?vD+=U%e+T-qyk$|lTq|Gaa) z-C-}=6D{jzYTUi;tJC29E6_@<`b5LJ#6;dR8FO?$^&M1IlRae?`?4x}aqjAC5gUx= zdVjBHVrzaFc*<~bdM&rF)nWdJGoSB!J+sO!?#4gvLq~peW}ka_;N$BbPtE5RrhGD3 z`Slvz=7_YQmn` zn^rS+u6gwP+vkqkRbqj&_d6bW9k1}jRd<=l>Id^T%G<5>Hb}gv_Qn6K^K?(|i#K!C zs}c&*)lio3`-zk<=IBbDJ*~ z-*5T8DyZez+k*#IJgt~e>fMO=oF@Uhx@gG*?zIS)~ynAV>z#%(Q?`Q zOH+=&Ms9A?Gv$AajXMHO71n%WQ(GzZaQ&t3?|L2ASg)8I2*2xbeWT#KTW&k9zI?Fk z;^BB^mavUwy&a+V8@})Q`(oY)rA7BFx_<;QC%w4kHl5K>)n^gA)5}?|8``D3qy^%O zL_MEgfB7rU-QsTd23hbR>zXOC@yt#)zozlqyxGyzy1V|^ldJC!Z;CgtI#VGk%>SqD zPSPRU+5Y7o-*fhDtW2Nsezx}A>dXJuT-qCc^LfTwDW=w?vS(w9 zIdbQ<1v{;acp0~GUgz_yjgR(**74rbyTknX-erZ7864c(Lkp6EDvOu?R#Mx0U+wPi z6sM0DQZ(4l8LceQ53LKoV0Z4O){S4H4j(gA&g>0aoO+D$#)Xd}J|WBNBA(W4ykz?| zJ$_AF>4z!}t5&BMGnuB}j9%jPy~N*F`|j>1VT(ggrOmLFeh)%s=GEms#~4JtCJkp{vm^>rEGHIo>-~3M72{`3Zu2;YjANExX4i4mrri3!`iozx&~MKQ9n0>U`g4zWTk9qN z-njaQM>X&6j5+!8JDPgF>vxI1i=A)(IbZw3f6+o zb9#eY4(}8vawf(?m|HQz^3Ffkzh}49upIiJ!ohi4!6%MC(cwpZ*xz|uQ-5!$4KDLw zs<-}T-=BJG(~Yf70#o$uud!=Dij<=`iWHOoEz9E>)~uOXlHd2ud5^${G|{FI-w&&q z@2p;baC6lE{H4|Lrn$z)f|nlN(Hw5LPlf-3w?xxBB~GwK`#z`!XED*PK3_ zYCY8bFW1zLyUAbB>ddW1NBtVk{hPcwEV+D^)YWV~u%t?1raA7a zq5g#~dHGIGt&E-4jS*GyvFn^eT!j~vy%5;X-f=s5!FO|k;ENqg?pCS1%Wyh%PR-}; z%Hv;Rd1u6Z)E3%4&+*B#e8FG0rq9^4P|mT^{o&+o!gktrE#Dt)H%N0cWIMimasCl5 zn}_D|xySw5KVM+6;O%R@dqBd1$KOr+`pg$kOu2R0SQe$8-}vI$)TVn6voBa{3D+*# zY-mv_+jhQ2a-O%@;qs&G9H;Jd?+_2EnDebN>C$J9wLhdZ+w~OwJ<#v4EE1gPzFy(g z4N({Q-7PCld3OZ-ZQC&GG)Hi}W5Lx*m(X~pps%4WwSTyk`2I9KVYXMy3|8IMnx62X zX>P~A53?$GmAz)Y*wwI3RQTP?s;Q<*ee3*vUX~gLE-I-Py(n+0wB?bMQn}&7IH50x zPfHnmUc9>Fenj)Rbsq~PBzqP0WsW>azAg}QmE(Wb+#lBk_sl5q|GulzuJZbeo`sdJ zw{O;D_h~%XT-$T0*5WMh{jT*F(wrnjrhPLh&-}NolC7`Ma>J=$M@6nrQXH>exd`t4 zJ87@Md+-0x(x<*VuGc8Mx+MON&Rt{Ay>TM9W|rL7+s^jwpXH*Ge6tF(7_nLw?rn3w z9$&RD_`cpvt%!!5-0ud<)5j%#{%wc#-j&{Xdd>0Vk?OFi^E|)j$bYmjom3+K zldD(pl*Zl7Uf-`?S?@OGf9~g(Hk0nReY%*DCCs(@5eydX^U`4+x!V&Uvoz5!KJ_R zIT-Al4_;7^y1spp^{<;>Co`wd*>%PG>y|YK{_f|Se7}0x9GC4!Ccl_fAXNME?}NzY zhBfabduEhN9Zs7srhRtv+cif2>b15QNB*1nV|L>Er?>w8Uy^RrJMq}X>Bo$x96Nf< zyyjgd&#cTlZjax)K3CfQK7Oj4X1c(n!gsgh>o1?Q%W(Ysz&pw&X^xc4xxK`#1+{s= zSu5(49{A+n_sUzMhc>Wy9_YWtlbFQF@*OmS;q<~flB@dcwT-!Je1kMu{^xMS-d+0Y zXVG7?_LDy@=AZ8W$k+`YJgq7Obyf~PL>oL!TmFi>r}%rz(xt3_x9?$E`+JvC!ev9B zl%y?T{O4!A{*a_pviVO?4r$u>dY@y;Oumh~ z8&`0h;`7=WvUP5&=aY|5*#fuqYcDOkUFxB6Ie3;p__c-^o1{B5_xt6pSE}3I_bqpU zs_=)I6?&?Ze)Xm#+3)SU#d*ZQasI6%@0liL)OxuJFMGSAFY~Fm;npYOOY*F{pYYea ze0f%{^kS+1gW1uX^FwzVRNb?FVgEJsONZ_O;mOkV1bT@8lc$oSQ$~cu`>U(oouV`I@HW`3gID_?_?1bgd|R`1-}(3gLONAN78% zzuCr^dE2mKTJ!b1;|*(scur^@bU3x3r`*t_*0@dQeoNfvIG5`ykG_~Uq4(Xv^N$Vh zSC-VqyFO{(uaxF4Jhxgo>E>3QOriDuHLYarG*Y_}iC|l0m8CGhmOU@rDQadT0 z>H2ct<4^sGh6#LsZ`(1X9iHEO_*HX2my+#jsr9Xk8aQ7>MTlfvu=D3_KVTEMYrBMz zxbF0Klb2L4ZpaIZX&1YxHDe>YhgJLGGufswm%bld=D@3JtvC7I%G%50^UoBEH^Oo4xqOq~**8|&>AJq0 zH+Q<&v!ha5w5}a8Dq(K%6BMv&J7D)!HsbkaukTV>t6OX1cx5J;Ip5TjaccM6FlP>X z`x^Ij~*?rA){ktW?_qj!tU`u+^MXfyHsFX`TH-1i-`nh|_=c1E#496F= z25fo9);d#G?&TMOu;*)?ubS^ag*=Y>U#9ushdoPT?;pH+_P2iheeI_uhwrQ2o89L5 zbAa=mnMq^Nj9Ts9Maa<|Jxt z^P!+|mK2MzTH~FMNB_<(%wT@FQBi=c%~`@y^w5VC&lB=O+=&4uE$YV|3hF%S;&<}& zOp(9LECn9tG<^)4OL>>|WFNZ;cp~N5hxFwJh9*K`*TTNs|0I@i_q=GGbbr^>ceN%~ z71>e;wfem;?G0RGKJne(5Vjv78{g`vG41Qx7QCm=ipgG+c~6pK#>ad&4JF z^)IN$H>VqEUCg{0w`KNjSwGJ8hm}6LYADAyD*Y?fSeDn6`f7H8M+Nt{$y^T}>}1)t zPg}8KecOYndV%m%;p$}8_{aINN%k6pIdr0eSE} z35nRT>`>w#lN%SSqOZR!_!i&9rzlxrVkP}IZEf@MTS6>3f11uc-DQziw$5;=$-hZ# zM=t(65-uSS%+_+*v}eVF++%M8Z@>I?aVz)o*ORsc_gjiipYW^7mhD*Dfkn&pZ~Zvi za{pOY%Zhah4&KFHcUitmah;Xt*L|?e@IY@|^}mTF>zjT!v`_DPwj^*@`R@&Kx^DhW z2}iZ=Uj9>B#j`DOb>hru&ZoQ5JmZ*8H%XO5Y>Qu2a{roq+TrJi3$H3_%*@Ewx-0H6 z_1*1FUf=6(Zuk1WM$7K2s@Hedtr;^TRXxA=yBs)E%^P#AjwjoyZ_=*>rM;UzSe)VV z3-}#nA(EXS`-e$Hwpp>ncSq#Qd7j^W&Mh}Mcvo{bSDB!`Aw8q@^W9XN{io>x?9zcnAskcy*h8VT*~aC^uiYo$KP5=PCLlD;k*q` z@hj&k@3>W7&iDMT8~(QW>55i?d0ma(choFWSG=f@vXWN=H8i5OoUmkg-taTP%AT`W z+ui10MtVyA4UVhj;;Hr9?_^K>tz*#p@$uvOzJm!H7Vk;>vOfR$^Qp7X*Wb4NIc44X zEBoX3JpXzAu-uFn)?dGTos=A2$HL=#?DsG0lK1O_7xi9lJiPNdQT+qCdG`-%oh_HF z`z`+c|85&YA*Um1KR4POnD`rX#)36u{8EPR`8v0E)ehjD*x7xxm12s4g&VeQY)r`O z4f}iSVEN-jmQcf+`vl`&u|@sMet9q4b>)>#(f5C^Tv-;mEB%s*?oF$DUI7QrMc0Ef zBz;bwyb>QUl~I^4zV-F-{YgGIzf1giw*1BJg?#5lLLN^%!~9}Ljrgte+Rm?AdN=S# z@M_PWy5UPK_d#psCjV_3_h#>0zi>_Kq26o#@!U(x)BEo8_v9P5s&=gXEjHOK z-r>qM=|u(e1CF&+E3}3#y{{R%+<(c2SKkAdnhSh6SGi;VCuO#DdH45ILto6;B>HV~ zoXhsg-GV>Qou3hRvEj<4*B*M86(@!^-oMxJL&#RhtFgk%XQkXlpCilvMVS5(unObY z%YQR*0fV4On4?|iq&=4#j?CRsf7EozyIqYhcS{+RmMaI9zn-|u**()qE7xChf8UR{ z;R1jES*q;&YpN8##^uPbe!(fheEmJSGd7f2yREN~R~Fmf{E++p!4*H>C!HyNpP-X^ z=g)V8JjT8A{`1+0*fy7B%R3eqXyebi>!PO^Y6@%;2BbEav^~kkcA3eb4Vc4&qCH7iKGz z^fg$XJt-zB?;ZN=v+1G(>jjqne!OK(Ywbj-u*qEuGVCRtw!3VZ^LL@jUGZyy^^;2S z{f?KNTi3KVLa<(Fiofdt#YEvZMXxjGOe)F0rgL|5*RR&A8LrP$CI1<<9Dl4}BgWi5 zGkQhrRtrCFwcnQwbynIh{3{}ruCYex+F`@r9CN!>?wZfvtorrwra#INJag5=E?)k0n zs@ZgPdV@*GwAPR2vp!t8aeIB^|L0FH9u%+%KOsNc{agBVlM-G_=t7A(yQOx%XY>Va z*I2*u&nxv7H+7Fodk-<`?R7u+d}_a;=g}p<)fShm|Gga)UFVlni>bN(UZLuEE7~_V z^JV1X_IAU#qzLutYtCkDDJVUP>543vMxSHcuIqSXm|ApTw z6(_vj$Fesq-|_xY`G+rm?*91VjF8QoKa97I?%{nt;d+AgPt^#veGCUzGc-l(t*N-zuP%a`29=wC%GHg)d*S!zvKT*Al#&Bg}ICwWDuM!tUx{?hO5WwCmTD7hGAvw>nM)ZPQ;pO<%R|*0Igs zl6X9B-fWQEy)OCLyzCj3PYTyFGTc zu&Rb{Y&d3}zP#_-RmING`}bXs-zyYNwANEjaaVRKzwq&;#YFiy$E>5SD%1Y-OxRxU z^2Pdo@~z+Zlip1Wp7Hr#>vnRz}P||s?pl0*mtg2<%!G-k(YuJ6H0JI;aBZXNudnaB!|gTNf3=@R8RDDHxM2Okgdbie8}lC@FZNYGu5a@@ zcz#Ib#@Wx``K+H}9P!~+tNshIe>!(hMlbvw>y?=~he_t#Ug8!pe)x0xNIgel22+a| z|NVcd|LWzQ_X)7Mdtd$XwD0KWYRL56WYD%OP!8R>dg5=n3y_71r|Rc8yW}Y4eJbB8F`36VoIf`I!M{)C68qn2_ni32(tIiFLi$TV8`Hfq{73ikFMrUx zfvbiy%u)Yf+9T}^qMwCwn&TR_)*I^V;a>3NO!I+9ev;E!5B5$IE>fs?a`23{eA>L7 z8$O&l=xmsuobyhd<8G1UM$ul6dyCyK`K>%vo73sIX2$#t&iy-z_D1O1O$%+if3xMu zTwV{Y>w&w~TJ{t(9^U-QQ0X_zp>2P~`%L^`Br_9(Je9THS*2Ldym?7J{Wp3Y5tq;>*O^k4Qyh!DegpjwCrU~0E^Sa%9 zfzc~#CHIOwt&>&Cs7-0*n^+UcIAPzQw{rFc)lA!6sws>7eb|1v zK3!1mqBf5&s^y&A$rmrgnA;CF7D&oA>K*hptjgtdD*M;LQ7XJgd;Q_rx~5jM*iGbT zxU6Th$*`-uP@Bdd-{E9?Rycl^{@F{mzh867U72=PSiU#p<-D`1YR{{KcMHt2UiUup zfrXJkQ>*UTSZ|Kp-G?j{`L9)m3Pc?JspJ{3U2DxjXKlViNe5&+cgcT~kqMV=30h;~ z|CTE|b;)lYj=9Z=b~$n}(`s0_120_fx@5bW;l9%1u3wQ{3zIp{{586u^;scj#)3G9 z+!-?ZUf7o(=Ui(FcL&9BRHZ{Eyk-sLl{FR0PsIhDTX z!IUp*Z$70gsg~Ltxlqcl^2+-qNgVvGmm`!D?f=%!xM-zzH~QjE>*p&H%u;Pz_!I@^ zbu^~_SzT~q?ULe@OSW^3C%=0s6>-rtYh#JI&Ww`(J~nfL?l1hfa%sEZr|cDTRqk4+ zziVB5+(bi0zInkGm+$lLe*AT9?u9=UuHVW}?-kp5p6RlLz1_bc9=2xtr{Rky=QbS- z_+EN=``=~r_g!;$d-rVT?@QWWdDNE8{rm1w=-vH7@B9Pg%UF&_dlxc7ikr8y1iWS%ZEmTJiWKSC)Lci*#v0_Cu3^}f4g4a ze&FQ64I8H~`8}=n*n$6Boo58yW!WF4c0K<8_DiguUH&r@p9z;-Et3aOCNoGK&AGY+bwsl$=?>A{?f6i@JNWn5v^YnzgTVBGV#{tcBTy3 zx5riLx5eKv%d|{>&g-&wo$Gqm{D&u(Mt?l<$NWXq+ScF)HaDuzwygcGbK~c^w&D{` zTm0j?=ber}eBzPpjG$lsM^;M-_=TD z(RX{}w}!l(ch=m0E1&R|svVbQ5-;tY-t~<8?DA)pVqxq%_APIH+`+K8B;WD>xq7!L z?{Ytd1c(^Sj9XQbAG*R#J?v%OhZ>Hz%!y_ddNN@$4UvMvZ?|6Y<2Dw4xo_*nOM5>Q z7@TQ8*ZTR9?S_QgGmpeaD1W=1l>5~8<-KyXWzI&YzR=|{Y^sC7{tr@>B z+q_cn{H|BL?NFcRch}M@(+(~9{mkoo;ChFj85&b!7eAcxNA1n${$Tt02j+Nw`gvtL z=#s7OD+NnFoA58Gys0N~$T@Kl=&F&;ufBv`Ij6as>(t5DJ(p^?XxzQM`PKI~Gb)dH zeRuu3Au7B(?DV$-MHgf?UfS#Y>h^}S6IXqk{d8kHkH+h(1*iUSd=m~@xS@W^y6sGV z*?A9K>knOFTN;a!JBxVO4l8vXe33T0Xz`j?ub$gZ_BZ?LX{)PU_4$tM%c(_D%FpNS z`|z}%H+^PY{+{~%&fCA+q;`CqR&4Rz2=|u5KF#g*nHf^&72@&w6r?^E3fCS^hY&Y19zqGblo-mn(@ZCDU0g)S10&+bFGd$e`{0enoT>e zh<#Z0bzi7Mz)}&lq|I6{-et+!n3_LWzOU(o@bnLFe(4GvJC^uoPr1R@z4IrWXer#t zw1?4fX;pGbhjy-0(oQ~4>j)l?o19CGH0Mv+aJk_|mzv8|Q-zY-r9M^v&429sX?$$| z4_mjV^^J#KK0&} zBX8%VGw-MI-fs9>k@YUGBKMDA;!@s@l?w_tv2&i%Ol4Csm>J*WSGD)7g4?PFeb28O zxAcZy&7bz^@o9}K{x-rNGbOh=_j~M;TX-d2V)Zot*N?PT*v7U;gjvr2x>43+*^->a zshkFDzX-YM)bji5-)ULE{M>NmC-Wa~{>jaY_CN4fR#a_IY%{0)oQCM5+do)U@Cj$H zQ;4|T;8}8CEsOEImaR<&U(b5e1HGX;)17SZ)_*H#t2nRev+eupMLQiohF_d?>&G>d zi+3&^Z2new+2hh)@!9Hc7V?`N*LaiIx4|kZ{*z@?KX==r$X)ptS^F-|+;BFu^XiM% zKT;g7Q4yjmK7M$0c!R_~4yX3c0KOTV?N5Iv29`L9yj$w^J?Lt=+THCZ!w#M^m+tDD ztoUX5w)5{rTuaqX8b6%J`B(Q+|G}IiK^${ul+14w%jQ2cEA+bf(h9Y^*AMl43lzJ4 zaMrP8zrUQaEB2=^Kb-k3?8dWD?@P9_!H&n%F75R$6O}n^=5R5SYf5Y~XzBmApKC7t zE!6lNQCig-xNCi@v~D;#znhlKHJVOe*A3n4Cvt+Z!9bzAt(a?|i*&lOOZ) zt-BM=^%%LLzg=fLYk2CaX!Vu&Melr?QM#s283H7D+IlC=tJSXB`*8aj-TieJm0zX= z&tDm{XTS7etvmcP3ZK0?UV451^5|;{cX!Udus3%>i*&wud+~Za$AQf#g5FWJ{q)Z5 z?tUMhK76=yw-V?Gfye#U&^2q%?Ur0`$#?&|T4MGc;S;s|8Q?{(y{}$>?(p(y?Om(O z!kxWE@(tJiUUtKcs~GLFnD>|lJ^g6Cq+0FVJSi^>TrIt)b`gU4mNUcviofzo+?FRH8$XW$$FpuUk?!)iCY# zyLD2f`j+46DX|VpTl;k{E6!TyDU;iO_oUXH*MffQoipXXEnuq7P~7)L>#Or~@851y z0^&>dPRfYZlGXmQ_58;OAFE%f`Nx;cTFqhO`2G0~M&Z{|f<=2c6}Rx%^7*Vve$8ON zm9MVk{tSWh@APNO=S|^!6mLA?-S!_FSi3*Y`@Lyt*EMP7bApBIUOqheOelKO{;mVJ zHvToaB$Ircxu{^y z?u(}tx9~S@^5=Rp?M-89;9iZB+FYtX=Wb0k+O2W-_Y;SUhAd9^Ulzm!@D+z0oEN$~ zy~u90*Ze|}G-a{Qs~^rX6+M_!$|JUA-dEMmpHUl)_HNoC7q-*X;QONA_w@u0E!eqX zel(l5+THHZk6Qx*q|NS$mBh4bnwZPYV%nVk=)sgvd^w+2@T)x!j^FS7{mQ+VoOn)e z`&q$n)x9tMos;|MN2F`r=O!tOG`SWd4R)v73LO5&4(Gi3yWo`|4evHIMGExo`V&$-hXwd-p>V>l+)29#6eH{h#CZ zSBpxH=e54ew$jz<**@#=&g*y+O8G0Df3DKmYl=qM!*FiTXRR;i2z}d++Rz z)B7$f#eF+1r|i12c^zzg_p7keBH@JOeEx&?B-gh4Hk{99H&{9ADMt@xtpb4C8LIn1>n0vie+OVEpa3Xz6h(?> za;(R)zJIwl`Ck9|HUmB3X;;?ezWM#)*Bv_^ztsnSEYg4Y{gr3S-g?18)$1>7GS8UY zYvGc8RQ^KH`}vu3?Y0>Lv9}sJ)`lBqey*(8^Mko9MdtVc)2$!wR?Ip#{ao)kop3bw%^N`;)R>GImE_pSj1Z z^M|RtCQkGxL3!R z-del1>F1590=L+lZfZ+@>n`h1+8ZSD^tEeXtj47Si&n6$HR?ZjPFc9CZ=t}ia}AH1 zf}+l|l!i(E56%j27mIXHG|Q1|X;(X_W5CaMQ){DsW9X6d;g_>{r~I5!s-7@)`NH4( z*_@>9Ih^OWzx^XSsU&`dSZi5Xj@tj-DPf1q6r{WOCMVppl{|OZzJd4c>2r;9y8~|M z+?8*a?A@u_d+^fiU%9d#k2%`^zFBLKJ?$O$%##0xQ~qt)D=Pbarpk=7TB6#oHb36< zQS;MEf2q}fcSUW9IkM#UO@Upii%ciK+x()YXX=NUGxGhOU(5>ClyUQID!qL?;r=G& zV->#=w?EU4d-+fQP=Z0pa+&ENZ=-*He7?+hp7;4bw~Qm>{yqCyeM$T2ly^JB>wZeH z{Ylyry4~UX=h&zn68VPh#r6l#&%*q*qyF>9{axVGFyYl-Q4{Ej#&|iodAqfX>~}9S z{I{bRx&q>z^Ny1G|E`}8=X=aCh0OJuzBvZC&a-lAt(Rp&L^4mWsyhzxC7 zaw%RgdZBv?>k+PZ4}CQ5Cpx6eWHek9b6)GV_tgiBGbBUL9oqI%z&$bd#;y)7=C|wJ zO`^{)DGn2y|BZ9;+lSVX^Br`ydM`~C+UluuUAa_$?|o*K&98p!ZeYn055L#)V$$o5 z8A-~Vk|U|!#JyzRY#&2Niedh3rEpMQHa*|_Y{!j7bU(7<#E4tP3=?et$g+>UArDe?GTAs>z3NRHZXcF+;i%$OAO!1 zHO~7(wMycD7+U%3IQe6|YcTUNF`6JVxE&L%a@?z`quDQ&-^4=qE-!c3+qtgb;S}C8-K$(* z-n(Wi+{L#Bbh?dqf*#kW<15#y+}&;PvhIWohphTR9R>eMS0cC8sNEG`k?8jJY((hK zw3qh|Yux3J=J?%OoiKNO+oBH6?>SZ<|+A3W9mRhngCFw2{?{MJn~enp3Kd#l}D|5w6meyBjyFQ*stxIR~k zmYmC}F%7sRan$R(3153@X~xzHah+d=FYOq0)AM#}-tFdB&Pd$&k0ol|4Zhn8|7|F# z!NjN9K-XU z*_XASil{C9Tk`9X=-uQNTA$N%#Pmg!`6Ci@&5ijzHff+ zfi4KIe17)_jiBO_to;ri#A@`yS=UR{D140_rj0TN1|pemei5x(JhBDq;#B56cVgU(A^Jw5nyAv_M#*1B>oV2f6Spfz_9e8*iK^ z6vo){ZovhkrhD$vs(+q+&5?{mGe*{!EF@NVF|!{B|t)F654m%BwvPW{ZWRE%D;?!CxAwq5rB4Q=9oa6Oct zv%Gk(*Yo=dVf)fQ)$&N&@AX?+sGe`zKF{-L!MFI9kL#}-dVY8Ei`k*Q*Mjf2fBy2_ z;MLssf;A!iCqvnM!kFK+=O6I*{(d^*;yz2C9qHe{KXVrS|NY0Mn9pk#{q|q)x^aG= zY}uS^Q)?Q-lGU9P4=$-T<}iDkD87g1+Rmu~Mv-$u+w?Dhj$1Q&S$C$0<1JI-wS1{L z)896#uKsloG&}dsH7Y{v`hwhTTxIum29$6$Z~Cuep|2yX{eb)D@s<1ik8V3Rv%UAy zUzZuI7fsKtY>X6q-QM|q%_N(>>6iZgdc5IO;Ndg2yiT_nI2KPkka?5whQY;ooZpY; z%!!i9myI#%J2)xy$a@39XNyYq`%ZcnIe*cN^@4}{5+iGv)*7$e%f9-az2KqbiyP*L zD|vo@WAkbC!!2p@vgML3VfzBU%wk;qb9aL0^Hob;f2v+u{pxyM?A(o){_1epPQIA- z&dh3lmd59sHaO(V^Riw{u8Im{dHyr|lJ--RV>%YaAHV4DlJEKcOzg$KzaHI*$D&U? zyyK60II9e3Eb`95x2Sg>RaCrN2fEGZ*pJ8OpeuHF@;|)v_wV#q`FaX!scbnpxi9xW zotp9UO~H>m8x61Ta$0}m)$UeHEv??4Wm^;ZwIuM~wZn6#t%;p6Ep*9$vE0An3)bGR zaoHc(eWk0rLvwfEicM@TQ_YpO`LzECk`>zidV-6m&eh9RvQ6bu2ASdpmFfaPd@a)% zC$2eV+ZuYRe~p2{wwOs@9?$SOzvRy)wIfsFj&A9?zu`}U@I3APJxA7x3vOR5_)yi# zW$Ft-=ikQzlU#4cx4#Gq^NiOIyP3{ZZ}WBXfs`7rOMIS((mj_LSlZ7FJ$Zkjd&x=O zmObC4)pG6l_g}AXdVX5naNCdh5}=EN*Q`9cSv%y#t`7pc&fMwc^NhY|_DgmP$2|`A z|KbU6qj`$oD{;vE6u48YSt)QQpC2dBcJ}s(a4`zDF>+=adA2oeW{O3`g_O@yB`JgQ`W;7m8tLX^)`2IlArK(rvlYe=-Pd{2M zxb3~vq5fBfOHVPZSMGfz^I<{dqdcF@CGWjIAM#f{`g{Kj<(#_8u#Nlo9Qc*WWG4{C zza#grh_0LeF3r2q+gsl*QQdIDj;mFp+Z!|+d*9BR^|PV2!j^B%o5F=;#NIWqu68(o zpDGv^2Mvfr=KuyS{>EZ~dMS=ZpYQOe0~tM-qUMXkxd9B1&(U|bm65b^Tf z#9!&PoH{>E1D=F?Kl;0?Dk3fPaLysGm6!g$SXvNQDPXqf!}%rEo-53?*R^}6sh+tT z?wlddv03h9!K{s}+kAGVUws>yTBCL-5a}AU;cZ^VsS>leN#v1 zq4lk*vb$Yx)y>$cCwTtmn)@<0bpCKmYb^~B+tkja;j?P_wz8vJGd|kH_NTwBsq%Nd z{WG=T+`_k&SH!K&V!gXdOAF%u*qy2FZ$7k4RVMb9qJ`Q`jl1S38-?%lF)TfpP~o~| zdw%mZ^SvA2pZfUk^7)w8GmG59IG+EEe`c~veeR(ftm%5SrK>(>e-r2E+5W8jg00%v^Qj}UEyrGlT(%m-CFL`K4Gr! zj6L4{C#`uobkm(|k9d9EnCi35yyN%ARGDi3PW{Wub-zV*ZhV%$cHLybj|=`9HWhNG zzyIy}(Ur}y+%A&!uf!|mS-+C5evMpOv#n=s!livz{COsS(7c!<<8w)WVRBG)nCN`Z ze}z+6)E>S!68u>1eR>DiHGAQo(wXH7}vXwo;(SlTo+2m=5Y+i3rS!MjvE}r*wjHy-1^#gZGOpNdIpVQ6!SvTg-tk-*Eyn2C& z*Y`DDHp1^jeRk>F@tx)oJ#$R2`NAdXLo1FJ#O#y5w&qaXzQA3}4YjUcJ*fFvuc%#B0J99W2=^$-4yyZ{m@=RpIzIdzH`nAujBdr@U!9;-lj$AT64tSwMb5Wx6=Q% z67Q`w%ib;)_qz1=!Osn`oBVC!T^X&XOGzzOdC`68?;oy+FKL(lnrw*-U^^~j6}C?0 zr*_6}Rq6YOw!gg9+i>U>YuPtlLrblRb@ee1F8#gFz>wa2@WaFgr zr~FsY_9K_s)S6CnKeDM$t6DzK@%xg?ya!)CKUeoLS$TEAeTBOdqZj_(cC13$a?Wo- z)Fq&x$?7@QJCH^bwx8Z>V`Ebuk2dmtr@i3izkjFG_rEaAe<^jt+S1uzyHj_a^25Kc z7=6|yPCd$~zg6YV|Mg3%^}96gha2DA$H^GF#>wizGQ)r`AC^A;9sK{_@h=?V(gsiQY_$jOh9~F!N?@wvnfx&`w%9uGg7|%N?_X9~=Z{oLG0$bL*0-}b zp!GiZ(Tkc#zK2-;s&K8Ia9-<~ddLfb@LL@-gm$sX$2p$ZEiU-wndPbT92I|c?!^BP zOXl8p;6hInPsCcc>#D~uWPX#|(p}5HcG(^t@%>2_!8w}K>UJ8;-oVoII*CJW=9U=l zM5d#?k38S+1?3Bl$Cn>Ue3Y7}yRP~DKW&a{OpIGg{?8E5KgQty_-`t|zro9U-}kR8 z@&CJ7b$0)jFF}lxO&jj_t^YV7on^|jYXLK&oeQcY$_}1RoLTC!szjgbl~lyDYYUwF zq@Lxy6tH6HytMakQ&B{)^vX-N!k}4TUY)L07p*o6ZIg=(*uvko=sk~xzSi=uri)6} z>-_4ub@ia)Uk1=w{J#}uus=I&xLeBW8vEpTFK3)`QoDQE@Vab!=;o69hqw=&a4m?~ zCLgw@sdiq}q<5dQ7Q~!i@>|(J+V}c=&+oO%4yVR(zUl7se(og5`lCk5y-_Ue=<$bJ za`exzpKH5tOWtYy%7U03%xPz5pZ2I?N*9Nce zi%QnF^3L7hUyW0jaf4{UJ3oeeg`(Ju~Nw9g9%6!N9A$s@!>mN$_ zo#&ZukaO;3{I%)-zn$lvUTS~&>vhTeBf1yu#`p&dcbv?fbnZz1y34m&Iz^>?rp8v(#8F zR`2&}*0|4}z3y?RJB~Fua~~_XB^kog{C?b<*0bi#dPln=f2nbTl~+de>ipRfbL@sD>o4*%QQ2qWqC)GyUQ0o{!_GN z-bXu`MP`T8zJx~jS4uu>JSy<#lS0Lk!#Xw0a#0)EYw!PJKlXMv$NE!h6&G#4EU7&7 zFN^WFHm9A|UX44O+co}Gznu3gAh_rKfBEX?jB>6!w>`W$>)uO_uC7=I*6Q$Zy z>MZHa8PY!Ccln-9$(d1oTQK$oU&yZIc99w76)V&AxHHdc(en#C&fT?tFTGE*@z$midE-jfGtc`Q ziylg-+}X%9G3MpI8PD8veO2uuR{CC9(w>-eowZAmXJKNXgxR~YH&x#o-c>HubbB8q zFz3M2#esqG@%JZxn!2n+-n;hh#96=cWByxxI=ZJ`X2qU==lu8WDLAA4Lg%OVrB35t zi*McgUbpZ4%Cq<1SN?wdDnE06z4(ov#~qJn8b6(KV4-l!WYLN5SRDme5Xho0x!#1Q z?}PLU&RqvD3fHI#c|PvCS8i_6xPG?+N0S0aQ-P4urN1Yquk!DIaBX**qLPtN*TJS~ z)9<&OIlgUCc_i(>)cL?s-_T6^j5iO_)6H~FJ$MViCq zm&qL35ZZ1~ax^hYF7#;Ww0Aq1{t0B6moXcp9lbtd=R0njU5|@nYMk?1j&E@|?l(Q@ z&mZ;=9#0kT?D`eGb(c-|hgAvdmpM*a{xm8oh;3tt|KH;KSwA-ZnseK6>eouiIgYDN z{GY$5*StmhRq*_nneow^Cj2kHVs>TU!l-`%x6}{EE&YBuS?tsCwU%9;tND^&&1#mY zn$LY!=)FVE{GBg8eu?{1_t;t`*Q()Nw%y@S|F?%z`S-MXPk-yGK3hEf%>A}9WAg=9 z=V}X@|8zZazD_jVCZI7-zrG_oZsGSFTbJXL?X(;BPdHG2@WJ^SJBycpBDq6$`j-Xn zzY_o7>%lbfa^+{YbTrbmMU+*ag}Cs~_&&MheCVy6ReP_#bM8KxYM`#$bV*0{)K2Gw zxqD)!zB?N;^IqJ_uodZobC>=WV@>Vw&}>-fYp)^h>+t37>cqsC_u`g%elN3sH0@2p z%UPNMekU6>Pq(c&x~`$*{NJAmNiXN!6AlXM{Lrd5WBq)_rKxN@CqD~X*6+45^M22! ze)_xJtLyHy@2)KQ9TvHvWOm1n-Pf{2KhCWQX4w8cYtf#?F;m|OFU#lg_`bzAR$U?O z*=iHLwfalHZsFB^AHn+hr}Dxr%l_rfY<{uRWZhz~*WPdYZ%z9l828HkP44N$eF-L0 z&Ls=lr>ZXM%E)t#yCo-o=8D>pm#$A*ekbOX%DsJEpmJpUlF!F--cwx&^@bLNXS~;Nj?x z*%!0_??18Z)8TiY%KJiUzoedRl;^uHyKrue$L;ICE_|$PFLHe_f%DMG9*^&!L=Q>& zAX4V|MtdnX_aD4pSnfV}!MRVUb4j7=``yxW4qT6gWF!kt!I%G@oSq$j?O|W!XF*9( z-lGX_pYyIOKi;_Z#p6p$ev7OR+xf@E^Se&K>ZxZcJ(HKP&r=WnA7g5J_W0lIgT!RsWBE2sozkldUu(A)+;)rF&bZ_4RjHoXpSo^)zgViR`NI?(R@c*K@OTn&Fh1GInfM+J6o%BuH@@Et&{6noilr4cp1muTK5U> zESu~~{`|gRcfOSIZ_L*hn>UL|u9tT^^~~Ah)(_jfn6H|8)&Fc`KE2uf;@hF$JF?C` zf3fc;`;uuF3%^_}{E~HiM*q#i8?w+3d?k5}XFP%%z6cEMab&h-JRg4+i+=<_>|9Wt1i{9*efFz-CS}$ zX}8+l?K&ljN?Qfmw*5_fVa2)e^0gJ~wfWWwouB&dwBY$mf3KFWnEI}N>bp?Odk>c_ z?LM}yq4XV}#QI&_UYkQSPJR}&%GcI8YPWT-+TG%#zl)COJ$d!>%DPbbi1o9YT3)VS zacS?NOM54~R>lV`{q1$+{!c9(ao-T_o7&+QY|?XEI*+AIv|F=)M^{a^{pXyA0k?wc zHMs>!-}^<$tK7Z4#}K{#L1pN8=gQ`SjItHW_9;(&_y0PB!IkUR&;L9B=&$~Y>^m!$A6@Ud{_lUSudiC_ zxUKZ&2UM~0O;?!nrlUk3T!e$tF^F-fQ0DjI#DbRl4*nN-`{q5+p6DSJ{$=0pgPb*Y zA=N_5WPyqA{P*qo87lj3Pr72#BjE)tu8V$7y*u&e53`i1R~2TmR>)8I6@2y6ul+H* zuB-g6Qn~zCWZLSNg>Bt&#TzqxAOFbCjP2EmRloRIZucvLC4V?R#g!~9J%6yn?!ViT z-nrfTjJ*p#uk|jt`Qhh|nl;SV?|##$@{8|#GV}FByYHnby7xMB+SzNT{k-tn&ivP% z=T7q{$)4Eu-sRIC-W{pOPQNgF#~5Szle6dV@#r0;N1xC5_sY@cfpt@L+`VSj)!!aD z{&)1b{X^Mp-5){c>sJy!NQu4aKlv((C-LRJg{SuVHL+IhEnux#7bHKY?RvlXtNQwg z9r7Nh6;EduzMge>$J?j1J8GI_mw9h4ymlk$%k3|gzrq{(ekZEz>{mMMeY~m9T}{_G z^Z)ln=b6+e_c@Ez^8LE-@s)GG@#$9+Hg1-BIVX0jhwf_Eu5Q-KUw2rJ{I(ZfS8~3# zaOv+5smb=uF)!^(ukOFNs5)LkXW^Ik0m@S@Oyf8|lk@eYw^^-a(_UQl`tD~RC0dpE zF17uVne?fjhXsm$7qwVxC2S0pQNO!gt3Xj{t;gDu{5NKthQZ;7bG`>iPkZ-$mC&B3 zm;YAXUby9SC}!{+a-^As|D@WiC90kt7YbTr6oHYcj+(Rc12I&ZoBSUr$4f<6J8c+ zPYK(bu&0dc(X%~nwMEtTO5JxeEFY@Qu;y#LWGAgSleHuA0hh=3m_+-4m-CkXzEpI? zH;(ahYx2U9xh*X#vjqcPZacs3-EvoKx%5NTJeS_WRhRZInsL!)AM3KS+6UH^yY9;8 zVb~D#Zsq#*{~b;Dwz@IO_SXkpVs7^o_{T?fI)3wWkJRMlXa9Qc$}jubyQ<{=?a=0w z>0z;dT)e(Z-f7?Mw>D?~lc#m2FZW&7QT~wqZ>l@6BMbN7F8DF8F()y5Q`AojbJh<<_0qEk6DEdFShn zn=e*gC^=;kGsm8JI^Ue;(`;vsmO4LQaD0cWM9>bV@;x8a@?yW5y6OE7Tsy%&S=*qx znm4cht3ca&n)O_=7Vr-qql)Gb}v&{_x`O%^+u+fTjGtGj?Y|udikfqh36e7Z5Qh- zne#)Rf5Vq^qHliQNZ;}5LU&)F+Sc=nk}r4P$~O1;Q&ST;&3_q-{AYWqQoUEn|4y@i zk$iaSx2Q?KY5v00{(lB7Ee8`eId$5wPW+p$yrn?BC&uyKNv2)=V(sF(+_nbkHy3Vs z$^7(<;|8A^#)W3fm8 ze)?j51+T*ZkdDzss{yin} z?Ir%Ed-uD&oqqIpO6UtdkMDX47vuX?@=e#ScT>90@?g1{b-%*-&&qDOb>egLVjFIp z4&M0SWN5|r1A=@0{$$*7uZmszPiRHvZt?SZMGreetv`I=UjMl9x!IyGKfi9+zk$oF z>+{jl9q*rB|9GTIKki;oEAQ0ziRy1BH}(EDtUJoc6BpTDr`@pU>Q;#@zc|>|t~z!g z^s7k1i>!njuYSe5Onztja9;76fd8BS{CmwQ_U7xM+$Psw_oeIZf0(*eV*4_w$?-oK zZN=9)e=*(3vD1qAy6q3HUpw|KOs)(OeY-hh|Mx{pcD>l~`_+V)z;+e;`(GTq!*yk= zr@en^anbNe@>I9C5!d|Fch1wkV;-`TUnYI4)8F|XY~BA}CCg3k`@gR0i|+FC4_4JL zwhZ6J&)IxYicR&Mi3{(H?hT7#XO+Z1TK*$pQ{{z42iAIiU%P3+oO|-eHl9~na$@<( z_d0i1AKDW>^l+T9yR_Gv*Zcc5$@R`V-|%UyHBNk)>-^(T>;|#lnQ@I;zxnKYzHcn~FV!RN zAGEak^wn~Oyl2|4{%qO#GhxHdmHyM-?d^#1D&A4bdggiW!rW6WE06me*-(=I{jk7< zcfxHpoWZ+oG~WJF*y3;aNQ`w!wKVsiE9&Q`gL;$2uelA%W!RoB;_R5e%WdjA-@c+* z^Jj0{%aeH2I#F&1L)*3I2U55WTBgtG{XM^m^BzCnt9jwGlD@2CJ;)hjCuRHf>(}&v zyybtQgAcU+7u#~aKPYbDmt`(2e4Pq^<_f%wgEVTPWXofVZ|;X5aO`iKw?JCP_F%Zu zM3Z$dYRj92f9&FhG?dj9mF~{3x%s!!G;g*UyI`PUfX>2q0s=UN2pkWEXz>`Eh5B zJu-Jb*lggb^R3=1mK-j7@CvW}qWqI$F857%PAz@;!s@wW%KndvRliqFe07=Sr_Z{C z_^{P_#xIN~yjlAA(D6CbRu%X^Ec1*xI&a#m^dqO&XRgmaWLq0oAzSa4v9kV8eR|!; zpNlLnq${uQ`W5@xEb7i2?q*dMwqlJbFUmSzl=H7)wNBXZJJd2ZGKz2V-EOg%mv*~d zR+rpgD9@8T&oN`8yy8*bOSY%q+Rd0NXA-}OThP;cMQ(gcT&cKF+M4F2N7$Q-^+j&y zX)F!xh}yt<%=?&Q=Gm*S-*&_tn)+}hzk2tV7{z(2HR4JUyXOCs;+gIj_Ov2a%KH%h`Jj${pNS%?q){CRVU7WUTk({x!T>|0y}fe zmE7$4laD_%IUmR2JGXns&*+4OZw)N|c4(FwOW^-sLuaY+CZ$ z;FkMEoxAg0=XaiaEKq-4szr2H#_XSLOi@;+1fq7xw#|t&*k&cYP*LzjRbaTy=2xaa z?0y9zm&hs%3Cp&uTEE_ZvCiTP=Ir+`ZR&ek@Nj+OH}&u>_47A18ub=ws?G|NWNLR3 z_?JgUDV_Y{PV)b+I?G)tY1*S7hp+3RxFh3>Ww+jTweck!j#P5zVL8OO}ZnEzsR_~FM( zHdUOzv~9Q4%)G4dxPIx3H7>Klm3nVq4(*khc2r*9`Nmbt9hfYb?f>E0_4uUw;rAc?)!%pc_x$?i+WGaZ<@^5dys!Hxc<=uw;Wz)^Fx>n3rMl+B zSH(Tm57__wzHOCLv7YVnwVK92ZT{pJRtoW=H&z=aeGTM^5A{Bs@bcde9?mD~GwVcc z`MsK3`O)8**=%1NT$%ZJVa%i! zV|8)G^3T;LZhf>hik$WRQeElQi~H6)TD>n0=am)rE%JYs?0DQ|ui)X;Cx4#&V$nIt zFXq~5EUlB+d8u|H`?EW#0k@v9Ecv}$FzQY7(iuztc!f?2pHnwO zR?Z}PkMP4z?uA>6obtVTqqTq61G?1()G>{OyX;_;3=evUrU46N;XC==~C5St1 z_DWcg8*omhp@Z?X!1g2Fx2x8aymx*PWS^wAkK?nY@`4h{{2l=ocVoqU*o=?eUc|}FVFZVyGsl*g1Pm;gaqjOy_beDhGb)}Qh%_91zZ|DSME7f>8 z?D>}}`pJG?N<44S-mM<#3uiygTUX*AsG2SI=c-}$L`P5E_3q_S*C)rNKm08=Z@#$1 zOS>j%F`MoU-g!>v4=R6Hb-(p{y+uR$zQ0`me|_?P^Z!@%oByvAzSV!U{Z{)^_TJx5 z(Qkggl7935llGqfPrq0E@0QoNzheE~zh?ud)W)?RoxShSj_Lc3KA8T>uz>aS$FCCm zw#gm7CUevvbl>vh+Kq{OmXz+1@$&A|y1V}3stcj_3By4%+uoiNqi zpvYHNZEsY&V*k5?GjCpA5HqRI*!{fYw_WBF-&%V7-oCUE$oHnY0ewlE~Q`RP~BsCs5nh&NdbG0pV))H0dsU_RSSeaXioG%JfFdk;p@5*q6{@=vJmVLAOSLK2NNTt3d*QV1!1+zK-rujN zdw%?7S)UH4#Wvf#RHsiunIgERGp>$SLLpUhez2!2JJ%7o0QIv(0|pbM%T*TN5_z z-CB{C2|5CsAKyZ26?i-yi(ZNIG){jmRV`+WHWzvE>VT<1$VZ~nES?0ce5+&|W~{SnOz zS2ymNs%UY@Dh8Oc~bPLW^;J)E9Or&r@u93eQq?1`eAz@VA^|W zmYO{O(~a3CiTfP)J-0J4oc}9oG3VdI89fWNvkeNK20mH8*70AJpDx>l*UT?$nOF2P z{@J&+RAS#Vj$>(JU@Ix z;qLJ*&K3V}a_HV-W%honv)?+R;JebY9Zbjev|q{ktaCTo&8AOd+Pk;2G}e25pSoen zJGalQw*~Gc^Gz|^xgpM2mUFY2;*po_8+Qn?ZM{^hdgyt6+s8?T0eoHxb_-| zPhD+f9lj&|Q$yZ+neCKNBr#bT<46!2hSRpJ~f#7BXMKkeKYw*ne(nOes{Dq75D1C*KuZxa^1nQ zTXOHV%WP!TYf0B_TbRnmlg{gUWWsTQz&EWEpCxa&xUFFRp1UT;SNvWjVEj+sV6L%< z*Y(Z}yIN)?Zd(J9;CR)iU*;LT%9r)AWjiP$)o#!%I(JuGcY3^TbG+7F?t0rj>+5x! z6io75c8Md++6b^bwCMYL(ZJ$R{sQ$EZ0`Oarh2IKYFFLA`+#NNJ4n0ykYPv3{fY~- z>kGcLoSAGYU6Ze@s>}RvVPb&J#Ei;Rt4s55b>_sIUW(jRetpFyTX)d~u`wU_^vTWV zc=_+U`_%QBH!p`S4?8IT;FsXyxc>~|zIrYvj^3SUS;iH&`bM{Zokb7-KFf}B`{$>= z-G4k+e_tr;pYnbGibTU4OAXtV>vMX^+;v;7>0tyMO6CTK&kz`|}o= zjZ9Ojc$TktWaldSB{XxtSo8IQt5tqme^nUhwy#+k*!sWmPtDqEWddJKCpL=GO|7VohH> zWZ(6>!G%lyy3eioGrv@^y^Y@Yu`$2kN4(c}&D2{)cf~EHz&${F-Yp#GppqNk|`OY zZ`LlYzL0dWtmXS0Ugy~|GuG{sHoP3)dh}iM$uQTOypK0&x#(2Ht}dz1vY%0HEf~Lm z^9x(G!!G^AgLnHYUKuAHI&Cn=y6M(4#hfFP4a9e4`n+vp5PPTVdPV)L`JAP{x!oKy zZ?aE&S3JR>ocquY|E-ty&c9WWDHl_FuRmj6_Wz{;ey_QghUzW)t>gOVkgUN)o8EI= zN5rNmHZA=fmKktPu5ZJ^e#Jw_iZA`T{O$b4OMeA4?yCJ)xhuW1sNPf6M^*2LP8927 z-rB^pm-i%^y7l%m>gRp)URwRo<-!N+-EmYYb>mjwX#aA?u7iWST}Y@m?D`j{XO~l7 zJhtsJ5qlkb)#DS}lIrru6W>|Bycf0PclLy5OMkwxX>xqGSYp>h<&xcJ8&9n3^pG}| zT)$(_k1r4Ue|$Kw{l@vLZ}@AP%lH4{$}ik!*RY+xrnfe}rj_6BH~#t`}dese9isb?X!FX6jyWeAfF3u^+6?`RrwW=6CXiMU9@({KCYLo7+#^ z{%LDeCL8uWncr$zXNgQ?#`ENJ=7uRdvL~E7zSD8na+%%#|7RCoeBSrm{j=91%a70X zZnJ;6_k+>zw9LQFCGwZ~5^LW(dOYM?G4&nur`<`#%E#hbCrmu=Y`Il`N?C2fx%vm` zFXufi4&AlBW$6;GvpX*R)!m$sahJVq^)jWZU)mdP)EETGA4@uQ$;)@w{t~7s+bj*f zZ&EJX$h|D-)!}E$H*WR%##X)H(%&7=6D_JGrd(!MDmt@ygV^Q*|Bbs7_0}zUzB_T* z$HVX9cpv{%Ua;k4qiH7Nth4v~3fx7%9=-Z?>s!OyKh>D*7tIab@Wg6C$qWDD7wt3d z{GPZWgT04wqg|I=bnyGuOIFOsxQ{EOUD@2Rq_A?o#Ez``=92dx6tf~#laASQU$!(k zuDyK0(KEh`FYmD*jbzO3WIrpmzU^h(K7+QS2bXB8(A>%NK5ct`QVjP3KbYJz{U^rnx&F(x}%u60*Gn0D~-hdZlhZ~T8+|KrE< z`wt%H?>|=k<;Pt8eaCLc*S44Q$sF0IzyDzM{LLl*RqcLFxmI5$@c-8;DeL{KoG-5H zH>|p9EwSsHG*47?vt#yxu<7zUW-SigBrhQF?&^jdd5-XX&v_mNSo4MNmEW?g?@(=F zR%qt_#9C+RtwwgK84HB$x_=$sxyp90d41_xt?T{EdZGjS1N8%EKRsX&Wk2cI-qKjv zWBY$tPD)$vwt4okg9Vv<=cIN^?9~2#>EBPjo4e~Aew}kZ@!{G^qv}U`c5O8=3%kDQ z++BXT@Oq!|mE<$WqyAgW`SD>-#mpy3FZYRM?yGt-g*j6CUii}L#aefz7rM-CnlMMw zK>r?_>(rNf?LEI=?9-azZo|E_R&Pphae~wj1tspgjb7gwU*3!4+`O6V?6X|QAHK#3 z8CThltzNE_c5L;AqSA_L(J4-O;e8!HXPds<=PN&B)0>XyM{P&d zJ)4;X7te2fxMpF1#>MUXIs~TOtjy!vuJvkjNq%97!jrzISMm&|1Q#Xx>N^}d^p-=D z@5INYf8_Xk4_>OZT$E9*v$uQUoNN8JzB}jqXBRwwY46;lzlvNgSD&>_yZEQ-;GImK zN5}U!bR@~IeeCtUCfVUZ8uPSw)5Da~WW2v0UU=b~+5D}Xfv$0DFWL5rJ}O!KbW*56 z{Cwx%R&0uF#(H~yrLEEYt*&KfyQ(@av+Lq+F&6cLp8|e1y!$u5b`@wj)=S2OG~2{? z()?AQ`TOQgsN9tH^_=aFZ@*>>2uZQ2D#@+QJLC3jv)-4F8B^a~j$HTM^qALolYP^l zJz2f9^6Dj<*AtzJPZwNGWH_67;MnlzR~!EwUOZ#_S7)Bse|l`| zeu^<)yT16o)B<}|eT7@as@1;h)*pUgx>aJ&EZ1LAJP(h*Dz1v{RlE8;csFBqv(SWZ zpF=0gOYHGFJz?qrgLP^Ck_R6?ztZnD>{MQxy-&`{PTvGLflKG$P zr@afV>@(>P(U(oG^E-W~Xukui)sJ>xUhMTD@SB%J zyAA8oSiUKIwTUu+43xNHHkf#P_w(Jg-J)!%Z%gy-nJ>Q1fa7xG#qVEHLi!iul0=pM(Bd)s8D zwRwH#_c!5e*@ByoVmF9w{^6(j@a@M#KDAPjyS88c zeQ8?2?U=2#G5?f1V)q13eb>81cH`&mhfDs3Xsk6#cxfWVIXTWv;_J~%w%y;jwg!iM zyw|?s5|>MVE!(oqZ4NtM>8YH#)8X+?n?dL4WeY_<^I7+Q3+3iZ^(bECFl~P@>)pzV zwV!H3>cZC2DuKSf*MD*Odxn-acZW_7hu5=lm z=leeKN>{&O#nZ&lp7e|J4u`&JU$gG;hR~}L8((p_t+88^CSm*RdfU3Bm-DnAvELJ( z@Tz@7#QA<{mEUd0kMB8Fv{&oK`c=mZ>h>vZc#`$&O8&iBRy?&^DWc$+olli`xbr;dmJ zea$v6>TMfazuMjGpnd-gf8@J}zN_>+KFz+^Q*Z61+FQ5EFIZ{HADa-M;buJL$7O+g z`DSZatwR?6xxVywodI7R$3yQ^3D2_LIbPa3^-|N)->;WDTGlE!Rh@BuV>>$`ZRXsi zzmvQ&RTqinHh$dHc_lu6#_#+6GhFo|Ds8)8tt#o_tv5;Ij3Lx&Yzw5ogyBkTkf0k{N&Z=k-NSt zp9*92zQp9a%YLhU61#oZ4b|NtFYmd$@0`7zciHP@tERrw74LUu>Xi;onCqoMtp{OFnklU~auKPvS-cDW4sGcFEm{ z`O3J;Zv92u^(IGO#Bw(Y@2KHWpZDPYt^QtW)c3uw?=nl+6zLp_*u~#}T+{aRh38AT zzI?mR=92BZ%l~7M>(7RUzmrSizuQlF*H|KUC^ly8-Z0-;{c+zJJyUBoJJ@deTPpFw zs$+|K*`mh}Bs=zNF`M(wYH3oRp8R|N_lJMw`5#S|uRrjcoiE|Pczp8x=l37ZJZmnUuLYjv{t$T__fP6@oL!G%R_O04F7EGJCHB42a9d}y zB<;#6WeLag?Q2&ZybyXNZED!TeG6yVdVe{*BdgY7#q$j<_ET2hC_Y-VRmtw`T2VcjD+07FU%{AC$a4yNx z`@5OAQnxnKrCPly-*y`C)i4OM=50N+=g=aX(=WbxMP%MgI`^7mf0@>l&FxA?xtx!l z=UiJ-9j(f9bZUT?mcYIQ8v73v zWXVnOTRg!a)zs;YUDo4^d0YuE=l$Z@JoT{EvrGxUm)@UW^Jm{*+VR}$&zE24FYRSl zvp(LxBFSFI_PMjAD*LIIKRaYUsw7RDa#6( z69spybCjVFtKU^2eP~vD3I0Ijm7Tf7k zRHXW$6goc8*I1u@N%*IQ@6+-XlM|&S_Wopd_dj71x%u^4eS?G-9xiS!tERs5_m^+F z^EC9tr>lQd?k-<-$yS${sqFEDcb2z&88lN(hxY$z8K!E;+z2d^tXSwxSjpux5nm**RSP2UJC~e zZt+)q-~4{V|J71^eyVPGHs3wq$Fc7-HayDaiLB#j4!@eD5kBj=*Y~W5P3sSC2>mld z`_lExRx1)$#;wj%ooJPO z>YMBX9Z%;c|4^1wx&M*DDrSEFjP9U|T7@R-=Kh!YwfC3YCtG`^+r=z;-}?e7qtamt#H~LIa{FYY;3@-@6IltoQ|BU zcW>o#>$Yk6?$Xz5&pe?Va4Ti?#wXL1b{$|XKJ3f5bWh3Ql;Xoa=aai?Hhh`EzqC|d z<9VOMW={P}-*#k1?=iXV<>GhS;q&YjVY{}+l}bmQz183`Z~t8FU+ll0X#L;v_T$n} zj??uFZI{^{b|#8FO5fl2A!&M)Y5c~imw(^MFnuaG!#aj5*hOZRX?EX~H+46Dew)7H z&zdOIq1dMSYCIy*hkMF(g zu%!FB>-qf`6X&$uNIRS8k#BCvwENke`L}rQ)CqL*zdbPX@H^Ml(w{G^Otn zs*ZI}X!XZ~;_;6rJGXy)cyV#X`{VK(-@ktU;okH65BB=+KmI%Z+Nwu?m(`X=Jo_cM zef@vF#bL5XHi+6M+<29>)$6aqsRLUZyS_C(Z`Rbg$-lfOL9#w|?XQ{*v#NL=uDNJb z5V|(%VcmsUQLmOu{hVWJdLll)S5f-4VV1E$l{0Uf?tiYo@~e)RPL@9?nJFw#U#YJW zGrRX(vbfUIjj|{1|Fr!z?efN=%ik^=p3+(E_)O1g+Nv$*j`wNbxvm(yw&eWzt*^fE z9{Q{Mbk>~nzbEH!I&q8j)#G}NyO(7y)t1y${;Lt@*gMVl^XZ#^49}gBo^?@e_McYcS)SKD&{N#7Yyy-1_ z{Z9Q%t&6jFGTG;I-*-v4zNC8fW|_iPyNLXG?r*D|Uz)J#zKd9y$M5~++9WNPyMMKM zru!_|XOyt>Q<%x@pInD3s?+_ zT`ijQMsAMLQ^gtn@7;b|v283Y^mut$d|hpPcZvQS4y+>t58pT3<6ptcX(Gtt$nork zJa4n|AGWHdbuLPlJB8ruAdV|ss(tL3|L@>MVI%)*e;!|*yJN%Yqa7W+%|aJt=Ki|h zHD_x6qW=D+)xIbGmA>5fuRi8wobchDPSuP2h2KT$oG-YlczLPkvrH5FdF8*(S$+A= z&}_5y#N8j7>-6K=`+k03@b_$ULE`tAFOo)#-E+S`eA+wv!Oj1A_kO;Ut||G!vCk@` z_xwKVo_>CrW53z^9^8+=n($k_wBps-tTXqE1h%jJ#k4r?pH%bo{~doq4>Wvr_+RP} z@AcnwR>S$GzXqpPOf{9*xk>Esin|x49uPQnVd__jZJYQGGdum;F!id$)@^Lb@hgwc z_H^c6 zX?HT~O}o6l|C#yt)t@gry~Be0=M*hCe~kZvVm6D9V&a$g0m4&0T;n)BlkxPVT!T#^ zd7oD??K$|&aZ9cFnZy4Xm&WQ%x%~T}&6_|M(aO3>?|5~#TWf{Bv`c$s6`{8`bn5-X zlb$PQol~1JM`uO2qvkcOyY9MG&Z|rAt4Tl8VNVj@ov>0`%pukU73lb?qlx>U>B^Nq{pR*k_kdynLAVXHqzv)OulF3vXRH#zdI z@#MR~LD#@Cg% zbmVg_$kkYC8C1_UuYIiy=M}px|AQp_q#0U&^~~BCIrW_{->v$-y&VhxUN5_3G{gTb zQ?Z9g)Rvsad(3Qy_r8~&_^wsh@$maMRe~%F3zaYJRe%(u98C&;WH0(q4#wB_-KK39%d`_) zYx?ltb?v*aJudxUnsKS#c2bQ^e^ga(>ZhudDU35s&X=rfh(ETd(zfd29OIa+FI;b5 z+0FD~_h$pPHNOQqRiz8RZTOin+eYN=?(dGz&-^dasRbWX?=h6_PhPT z8Uw6;?=;~{G&zkGaPsWPBkE?7#OF6pl|B#v-Uf=pqzrO#EX#0fyphm!}i}k#z zp|))6)*OEjx>aJ|Ck?j?MK`2YNNyC9a6P>-JG=1puBbczym;Tm)Qgm_v+cMY_mAoE zynFo*`QJKTTXenf<;-6%J8 z>kG%<$`iMl&;91VOWNb!%DXlb{f--44qbe2!UC`Fc~-|tWPU~zE&c6M74L6<|A@nT z&Ocun>g>aJ`!BCuQO@&E+Vk=LG>6BVOXfdGdMS58mp%9U?~M6|B5u>(^@?d2z7@Dw z59+lggt%78PZ81=nayt4XUie^RcXpL%T*=$Qr|R`y6;<=P%&3!crLz}M?EyDZl*;fchKdw-=w=gVD1?a#3VhU?>NP&oeRmJqo7c5*T+ zs8JmBbTVR(2Fj4wvsT}{l3hQ9W}FGrc$P6|zO~%nYyYGp9=dS*?;Yq z^ICtah`rOyl$&gr70{p!N~@OGQ; zf?w+17T@`>L|Xh@wbb(dneERy|6Y98R#M{ornzU2phWTQk~5p{wjU4Q-{qUXul3aV zeTR16mpk&W*|g%bb;9rG2R@a*-gqUvIdZ-H!6&`$hIPrZ$8s3lf{*30sO2oW@c5(j zE5o#4S+~7aB8#v6?=Z~fPx`ZpS>o@Z;}Sow+?I%V<@V~!(;Zb2oV%W!IUMP^eeqM# z=8K!6Adf8vuEli#gw2bq!WJyoZ zANL6ng%XyOpa@FoOF+N}8gG9eD!Tf%WA?7FsqaMhDK;#vR_~APTj8YN^Y>lzB_nRO zSH>w%CUar2rnK)w?=@xa#Ju~GUKu8b7As#&zYy@I;<*_2P z4>uOG`#b+WGclvEIQ~e?)UIvj-ws{<`PlHsyK2MIPG;_Dwp_n2>nGnlW&Yv7YCVf< zQy14cyN1ACf*W+wttW-_Ce?&x_Y33;T1WgT(hX$rXn`I41f3fNAjj zBdNmjM}9hU7gW8M%qaV85wTvPX~XI^g?-O0=1j5`ea<(tUwXN1&l`4g#rIRU8yxs6 zTXZUWV(3E7Pbb$^rF=NczU9fPFEu?z5wn&*uUYKaHGkXX`YE?Q#WO#sUA9;M=xW`c zmf@50EA;iRzWQ{<>}Ghk3s?A||4-}wzi<7Y#K`^qkHyTsLZRI7c&oUT+4tO5%l%E< z(B@zmSwHEWuJ<qEI^_% zzN_lBT)M%@U9Pa@xsyijX2GhvA}T)7{My?W{tJ1zZwr@Ny6U1Ae~;Pu-D)$Dxc%Mv z#*F2Hr!MX7U48lTj+$hKujxeK;tRBcNf}Ei562xyJyy7dB;Fx z`o)TBP~$x8u=wGcEO8CRz*Qyo6>nTZcU`w$ZgLedj7CJUd!m2ekDfg<*0qF=L= zSxa{HD{P)KZJx5nlD9sevfrOvVD~$iZD*fS{%giBw>HR|?+@ubEq|+r`?+0bpYrp^ zXV!GSm|?<_uKMlpP21uEza&0AkC*LwFPRGij|s#~u@ezj;Xi0;%l`OGblZb3a?S-& z{SPy)*Q(sQW5cvIPoitu^>6O0Uw?k_(ZOVYp~9nlxu$0)7hhB>R`QcJ;FeXN_TUk# z{-ZYcZAzIx-eDlu2eua-R4ecUngHoIYDYsc*UtZK8{f8FG5 zJzBL}+|Ya5p^~?AXO+y4?fSvQuk+1oZPLEO&X21Eypr9|S29nNPG5bgHs!lyhGF() z{iVN`h8kRyQ4-&s@aApHB^~Bt#%xNDu00n_`+Vunj!kvVCFiexyUVfoE$7T_l5OWE zFF2Cw@Y1gPSf!}q74`75{7Wk%j_qY|Zg&j0^`+6YoKr|F*KtMfwyE0}wrJm-o_)#o z>~_iT*SE8rlB^665?6Y5FPEddD&*z87k9jGKXcmLSe%$uy)WwJzOyTrCzJ(pp88h1 zxY{ps71EsAqIH<(AF7bROyskk15TrSM zsNQYK?_g)B9%jHm={_uk$}pr8;ZkyS3r0Z+eUr<{o;q zSIfD&U+HMHulL$2tM1!br6r-)t}O}+=C}Em`YJRqPWVUv^fmSQMloMkm8}1|kn@R5 zu20q3@b&jZE?f>>eOJc8^JZzwwuc%<8ShSRc_Pg&wAVrDZ0q)|;%8TfN9u0+v2j1& z%crGV*0|Y!@0Zx|v-nGWBEOM*6|=33cH!Se=@)a4{e5AwljC_`WJBNK_XlS>FMGUY z&+ZHB?u%?WyO(+Sm)*i|KP`KB#qs-s!xJhkl3&!s80-_6ekZW&^m5yt)z55Nv&Hwe z$bHXsdS+j9BKEu9ulv`{f<a^a3S}$ zLq4y2byf#gAARZWbI@-_-aN*o&T?mJ-*m*kxwNEg+T)#v_RU>dZJ(5$-}d#^%K*QV zjxL{KOlE(U(kZjw`Zyzb*2>x^i((Cyi92P^WWCI~Sg`cDR?CH|=E=uO-YwfU_0?HM zw%yVfuh{+RD>ayBFXk0vHDw8FN8AS9uxXz(6So!3aJThN{?>YFm-(66w;i#YWEEeP zv90>``7E1Byx4o;IZrvxYAwoL_^tD^VD0-bsoT6uck~>)@BIF5=MP5Xgq%vVxynfe zW^?uBYVuU~$je-@C9RMafUh6>Cjwr}(B}wQ8RozCZd>s=d--J>1T;407TlC=mDuoA zIptTKOV@Lapu46oLc`XFIi!D#6WzYI)Z^R6f_jn6c~S*We8fj{CZH+qJ76U?b2Ffz9ez(x45nTTYVSooj&jP ztIYZ<3;VZ=?R*#Y<#Wj5_3t%`Y}+qZKX?3c>zC;z^ZL#!z4H~GUhSW-=UuJJ+FE|k z)!!8}&(W8PyE|k~?Y&X=UAgX{S;UU6J-7Ot z{uo;YWrPz;S#2Wp2dk7vsqO$?G`=HZ{B)ibsls0&3^5BjX(apG%&Mg?owqEFWD(^ ze*Z4nr}FZbP*Y$+Pa${B5{3!9f@?k0HR~_#+s!HgShW;r+n9k1q(EXKycE zdSf%l+V;q0 z{$H*Jn`GLajrCr+@VKeR$7>2Vqq|j1OC1vnqlNc|TFy`X?eNcM>F@t(bz+k30Y1+aJ5E^=)#+tiS*7eQ-}A*nR%W zES2e36_Ul@#{W9=!B@Ce`{m{xMf|hw&f#q~mFe=F{BH4YU-yuMVKc6HeGfIT(hgZ^ zcO;DQ^bhfbjO)@~-6=CRb6v^$y3lWbnCShug+;Re|ECduI*qS;%^=)|s(>M$?JvxWzj^_cC0GpHPxt zQgPwVOUD}z+7m321*Ww4wD4DmBzEuF(zmplUn!IC*}?vx_}T??uE}3}=&x|+*|M*B zQsFSnpl%Bx)Y+&4ygm_{ z@%dVwh1ZpD#Kbq4d(9-A3OZ!*FjI9YVR%4)6f-NmoJ*5OR9 zcEU?5POsNGE$O-|2k4V({jecUMzBFB2=RX1e-n zr_)}wQz+Lyuj*Wpd)1z50dix2CIDyozUDp!$DnE6=}f z&Mp1FLa#hLw*0g}Y3VMtyV`0Rg>MDt*7s}Om5vArKUy?DigDi3-xoUd4_{blKV@5_ z!Sfq}!2xXoyW_J@7fw^IiBvt*X3ur_CG&zg^ZHvJ`YYvKESu3~n-#w*!2fO1PS6s} zg(WZ8gZp?ny-x{*y}7)kTI}=6To&)|lcyiPQmQoN+FpUF+5BIgwqA^5T-m2WzUr|4+JnftRiL=HgFYQVN552$3 z#C?B_L$R^NYJuP#tb&ok2_0FSM2)H*c}WH%84VF4NvbZYyWZY}`yI<4>>|6gttTf1qm zSlzvVk8wplHMjN!*?WJ9d{TR7{~|GSyPeDv!V8b_ zh~1*=v1@tRm3SWS?|0uf7DdI+_$8uP_tCFqQ%QbuVC>|Vi>HMtWL@GmD3fAd8mgA` zX_`{mNxm5wb5F)`GH-qE^(Sy?-I4p71(#{RU$FD3lFG+87yTQnJinWWb$?{by036| zyN;G~MZPu9@{cm?igkxhbLc-R-O_8upVVbzkUBl%(%;s@f`+NAPA^KEn^!;FJY&7* z_pJF}EM{Gc%%8)zRC4S4(2ftRw-Z9YcdOk!E}G|9@vK5ZDf*y@Q0MFXcb#ji?Om=u zH{JDo>M{wt=dWYAr7o#v#!oH#3TjaPV@8YQz4C$3p)FASLI%C+xj^d?#Z}+kUsx_9 z_36;H_oYYAOxCU5pY-w{>z3@BOm41@SDu+&yQD>dhOD0ot2JS`g_l+sylz4`29Mg=4{Bm z`5(Nr+TC)| zw6|^%|NdTCH$Up7o!``V<%iaAy|CZjtzB?%PKEvyW3~fRa-Hu?isg9v>VwYR%T;1O zm8XX)CEYPLm}5HS*crz&6WRs$RxH=rH~Wprex>zVFWs&7cFf-YkZulIMIPuhO(F89A!WE8P}zqHq9o`_9zIifv&yH5REYtpV|viPs$ z#cUg%i@6D0{Rd0U?=`*s|L4b&N$-BnSzoUEL$mXD8H=7gXsB{M`drdqK~wB)xfheR z{1@H(_`(Er^Sb4r!up}t_jAYN4ruD$UU8|mJNxp*WLMQqr(REZ)ueTEc~$m=7xq4u zPW{;vAFVvD`>*Ow2;-Lz%(i-Z<;frCNo9MWw-~ z&Nuz3zsGiuzs9&}SIFY~eU}yPyD1*n8=}^J-lq`syuw_o~;Yr)nD+=?Z=a;26OJod(GzAYE^sXQS62rr%fH> zr@s8#p|jR0V!x1t-^s?JD7mCI`*mHP($)#QPnj4md@b~)-OD(Q<4+VnPee^LK zvRWr{O?#Ia{bS?QU%j)xv#l}rS^o9~@6$aGGwPm4MR5Op_2=xBaGSGduN+?&_QgET zNbKR|YnNZ!=+BbZ-`Qn*e&dwY&wmT-O;KNS{oc~BUHmV1izIfJt-QTDAYSM0c8yzY zC(aZa9NFUNQ6f{dBqiZ_bo_!(o3lV$MtSCc{<@Bp_vNG=|EC^!>I`a%mm+U1Y5^_J zSOi&~fu&jYwmJ~hEW7zBzXX(n|NoKFsCp53+WTCpSj%a)194_x`+5-DU3lVCOH_*js)tSn}x`+aaN!{%@PK?tAz3S2Z)+ zmt9q!`9G>*x!BLptuOxlFmow;?DgF+fG08F*4+LTg{~XK-d|W!{rmi-+8xQ}PU`C} zEcrbx)8N~-l}vlI?yjDCKXRA8drBtjp(2?m(Vxa_N@-6fcSLPsRuj7xS@Yj>S9$iO zzbkhdq>3ue(!Oi0m9XTi&Pi>*SI19Y`kQPp$CTw1YqEg($5a>5sujIz`-?+sj=ppk zvF(%mZ`1Rfr}^O(8<%Ig!K=>~FRf<&zWm$C&z}`re)Z1&EakMrsP=W7LCIdxv&-@q z&#n%(R6kb=j8{VwqZ+dC(*ApKKL_axyk=pw4PlL&M#gwXkrMtBvnODrt#EIPi06PJK?@&Hg9b7uDt6`4S=e?x%4`cVnpO zj+YOw3*KAWu6B2}(!wqK1p_6T_*@+Qd`a zl$R~wQPkO1ELu73rEE;>{9li}zRR*Zzck^yI5|vd)ef0uY#;AF3tjp5+G2%)1?cn)}swUso_;T!gm#4ATC~?m-za!^vFmP{Q_;ukH zUESyJn||JU84$JnaK%Z9mWx%!_XQk22W!9i{Dbe+`j&6LnXHS%-!(kFl^GEA#m(bh zEyq)>->XXA2OrOm-6I>Qxj3;Sv-d@d*bDO%m2G=|cD}P*x)RdzYAf-)^PY@G_PpQ6 zUdy((_00J?bKQ)}O;6+RGjMy$JEw%m_vca>*!>K7=h)EZ9N%zkjnc9Dy%dJ!x5mDfKn zd|T(Tsl@(AqJ-b+=Ax4-F86oJJ>16is-3lRo!NQN){pMJ9P%D-Z#_R)@GQZlw}wfu z%$!H(ZgBPFsF(9@eY)edO2GcDXvF$mvR)-tG(?^ckJ2}vg|KKH5++-)H{pl<$I1 zrpvp1XE#?~^jSBc$1p(WV!*k*LKC;&=lAa9<_a%MKe+b>|K_{x0d+RNKB+n75cCTcxm-3^+~Ir@$ZpuE87{cZkKeRvDxa+1{RrnCu_eh-ujd4 z$gLWV*X8m&Z`lu2yyH01Q!VyT=dSf%15tC2?|EC#Z@6@~V_z-nndv>jORKXV#T+?T zE1Nd$MdqD?towi7`5Kh&^<7<3|24N`hVyIL-7Dqq$=?y*s=H9U-~(+#OUJ5644a-t$NJ$9!|ecS)v+;@w;f_Y)}^N+gk zB-GC>lUvT&ew)ASdvt=c-suYe@G~|ygT7YshUKk3GUMdsg4!UpbGsg~%_+Od`=TJ( z>YT@$e&b7RkM7(#`NAZLQE&HO#yeX+sD7zh#Z!CjANO9f+Lk-A?H8_de_6R8gmG`i z%s9R!zZzWp_s&w`w=KKQ&Qde&?dypz*EvsH%_g}nYe`|~pM-jc; z)w)V||Jw&2yLjZc$i4gb7E8p0_R8_^>pP)myKqL*RdZog#2tLa$ zO6Cd`l7ww`TnV@ zlyIiVrw(oLs~hG&TNOO{;VSpG<@4gK-8SSIaZYzOa}G-k6el+?MK?qpfcu}ICXzaV<1+Ffg{g(BfWx4lfX zzbI`gH{_B1b@;|^fr+lE8>}j2!w>1VuUsPZkWYTiO*4(PzZI4|cRF(JHf#6Vu4BnB z?=mlKamr2Vj`*^?Z26b=m8UC?+}|g&(9d_*c9HjCOREAKLtM_@_4=KpzvgIl$j5sp%q4GB*66P} zone3W76Y^QuiRom+i#Jxl)3+1nSZxi;MTLW;%f)5m@<1+7;nkA%~yUlu3_%`nx*x2 znN1Ii_ueyIpjfk(BgPCgT=u^QdH>&sa)buk;S1axN72%(hzd}Sl#L(`;zW?E0?wu~$e(!NS@A=Q)m%a@9 z_Wyos>+h1U77Qo%$nSMGyzpIlokj%TYsqkDLlo z5?8#w>*?~zCvj!Uo$Ov2%XdcH*X8r>YYImWzTSDMc52kh`!8-R_5ALA-{r-L<%t&6 z22(EoQcV2N+!4L0`Puu>?aH^pTAvArA1?XEp|RFDVOOP;(zCqQo7=<{k5(t-PIwo& zO8@JjO;es({T0=C-rrV~C6|<6%P403);@ICcf})~x2w)e&9G8;-gr#OYN6!2edUH_ zb6uvs`x&#oocD+2`=89yc|p0`9=#v_Uoq4hJY$Wh>p9c~V<&z4@BIGzfwnuTzn#J_ z{Mo(q)8@rDeuv7I)dx0%j`REZ&C6zYqP1LIo$9mOat`k%6z=#kYu=6zpWa!#ew1B! zs&e`w*XP+6UhU%9;wRZ+%;x9#+_QB@*2Cf*PdDbi*!xYF=f+1-w(7bA3HJk^gfdyi z@ZRT7DH1LA*A0p>-IB6%*F2^r6|W{fdgd_o!PTzkuO{8Tt~fWfY})3PYt^zY7jIpE z;PZT!a^EWd<=>Z9SM+36ojV)D4NuS^cIew1|{;WNmEp^4`U| zR}Eu+3BLT--`+1S|2f5L(r&TLFFPee&RzO@dc(XrQ4wbmkz$@TcdL}reofvmrD~x? z#FFoa->hf|dg8WXugtTJ=bbH8#ZP7aO_cm;qSRGmPYc+3d(Z2gzF!+u8>O%d)HMaybbT=ow zspEHgmfw1DW3=cG-=m^i;_4NTnJmP{HLc|;O^J7E&j4Cr`66lf4AgU_}ci@qh=w~%k{NgZ#KzKcy8bI z=kC`R;gRigx~fuECDrJ*rhctjGL3QNZj+buR?lBP^{V3BYs)uVyk8f(`g!r~DVw5v z8`2Z)6n2@f@z!4LxN9|=HE;BEfDvn{pLBN z-Qr$_mOV?SZnza=uw1V}Xgb@{(-khhk?jTRrkz*XHTjp$!&5)%j)?9Sn7d!3t(g7r zmD_zgj_S4Ch&c6pW354O{6bf~89~z>UK(&H)=pEZY2*%Dx9WWDO&f!tc(yF@jT!1a z*ZrD{qU3zc&pQ3Nb3ManeU);Y*7k5nUD0$;}Vu5=?#(nuymWwac96H^h zbA$D8ko}Y(`9+!0(fqYi&$j&xxW(6?xR>MEp|=J)kN-L@w3K=qY9RVkAnJjeOm6)N zS$#*#d=sD8JohtmUnea3d+c$hxzZHn#CusYtiQJ;bvZfB{uc4+z-9HRCFh@4?ae;m zzcAbN!6rq}SbsX&Akr@TC2T@S8`VHfm4E6_j>c3yZg{b-o$W|Tv&*Frp&viEK*Nmx zTe>IT-|6!H`+)~J)vqV4TcY_hRm;w{$u|Dls?d++d#f2{ueLi-b$$PbBcFI@n62k{ zSZ&+o^B)7f3QE4Ge+7w_qM_<_S4^V zIzRg|-BHu{Be(UI-PVa--|KaFFV(WWOm``XD*Gl0F7e> zEpfBDo?V{jD&hTDXU66$=N|1n+s(0D{Mf^OC(BZXp67kePu}WWo__dE!NG*$Y@=V7 z?iz@_ZBfkBo6^FgaaT?G)jQrSYu{bp1Lwyu-fQ2HWUlmSj=`L{{Kfj#kHj~YoOiMd zpY#1tR8->x*DKd`KQEkPC$g}TUooeXJuNDK*;lUK%q2S|y|o0kCb0K}`z|fJ68=hQ zOT-pgL6dnKXB^w~c3H@!9ib+5qPgy>PQP@U7v{!m%vDoL{FK$Auh)FgGdDBu;+6dF z#QuXNQFc>|-4EP&Zx``a=Wg-Vy7^Hn?>|_Ot6Oq7Y6ExS(!7>CUzxs|O0}&kSNilW ze2cW`oazN4g-7e`qjclgPcPLBI1`vr`bu)g|D9Vu)ct6Fw{yYD*7F~$p8tFBVXIQX zO>pWl#+Ydcg3L5P_d;I#ed}oP> ztG@mJ=*E5K=Fbma<(Inpp3GnV>GI+me_hu`3I6WgE1%q4FCTZ~iQ_|M{iG{*xi2bb z8|dxZrG9IEvEiY|w+~5OXIm(%=kQs(`q7sjMT`0evO8YsvFB~r+w<=HjtjrP_D_6o z-?Ya|VK>tY=hG$f?XKktt*ajd8^ws2?~Pm5c`HV6e(Il)_0v-FZ7033_1yEN;Du@3 z{#jK^Rut99Ub-9O7(e;DwASm@e*-RQ&NK;Iwz@B<+b3)G!MC=i0j_oL1HHCJNzR|V zq;2)%>lc5NzOtxl+WF+i3UlKNcIO^QE54BUXz2Z2_Sl3|&O5fH=sKO-%hok<>2Hsg z*qNQrF3)#f5tVDFaCdshfj1u%&pc~Nym2@n!tK3<&^1nN0l!=wKl2`)1GlvjqIOBQ z9gSXi$4J=gvyVo&pX-&L>o4|RnXh$OUimJ+M&rXhiU#S^7gaV+zg4)jIxG3R!kR5I ziQn@YA4a`;eZ`UEbiIUPYhKj*#)F>4g%>9MGQK2}^enIS<)qsSx4d2L^<7sy*O}#O z{-(fqKe2a}(K{y9S{|BP7qPi3A>+FEsjHVS)Eskj5v@qj`gYkYApXm~9fkdeE5hWa z7`Gp|QLlNZYsG&d^EF59W~`jMD?dZd?YCdc-Eg+gbN!uP8VNnie&_b(O!!u>?|pYo zwtGcnylL5aI&q(hpjUuju_Bv)qa$- zHa?U){68L|+X&t{13ot&)Yp2rzl-Dhq-Aps9FGuSam;Wk@vHeRxA*=9i#>W5)|@N| zeDv@6*KJD`w~N;-)XwtySD|IU+U@Fd(^ro-ecyNZnXUZeo8e^(AN*Tx@NC=e2{Vlu zwlDc|$Tj?a!;awdZI%mdbp2^;sf%&e<5>+06Ku5LO1bLXO3Bipo_^A11S>+yZRJ@2L3 zst4=&RX%6`J1i0MPkEhIT;GJv>l%`{nv?ompOn^Kthv^`f66;qyNJkL+jl2Qd|{YU z_V?{K(t9xN^s4cIaFaC3){{Gs&yY=@tgJ6WpwPkp!6HT6n+`>%qF zLBb!-Ua`x&mNw(uY=)(g@-r@rD>SSC+C*>3Fu|!KKw7 zkGHF1`uerC)WXbaf{GhUFK7aV3bk6(#KPGOK`gd;o{v)qL-#>o&^>)Gc zM?qiYty#@gz8;f&UglV`IykW_pEr4P8Hep=<45}Y*wWR$A7A=d{PC)t=NBJ$w!V;l z*80S~Pm;fW?Q`F6o_!%ZpLbIEb%o4wT_NpP7cK{PFWDruWct;G;@x+qq*V1zUc~i@ zH}vgRuAeby=Pl;?E)u%qdq$HT_n*7ktFC_eaDC3hwf_oVmr5y)j($t?A2q%kBC!wVa$|m#J+kUT@AGBTTp7fi%S=pA4-rr-nJTrNLO3L9mZyoMD%H??a z>bHsh(kZv}pLTuUov`q`tNu;4*QMpone!M`3-y$q?`1K_3yBpebwuG z*W6p8zgP6~pMAErVP`PE#{$EeOA-kh-*~)fsz1Mc3G&cuZVgj|#3uKP} z%aD)(&CmT(mP~jISTQ@YPIXIiJgs4eBfE zU!_Z~)lYnRk9YFn=74I;q<0mf$AaA*GUsuBUeGQOxq~@O?90wewI|PCs+HKQUUe_q zAl@u;^^y5z^PlaO3`=L_F{xj=L`cv~KdH@@CwccigAxg!{`oE;2mNNewYzofZa_}8 z<{q8)l{cDi1nD~+vtfHWN3i4FMuxM`_Bp=YV^SvSbIxnV+&Gtee@%L1*Ec5hIXyqU z{^+{x`~SRLA7=9O!_L_EbL3_Axv}VpgPP9yC?f-)a^-m8oBpA#h5jxn{*lIdZ~qpb@7EJ1{AZ6Td>_1F z-Yw29MHVj)ZkOeL4u|%xKlExvOH%UByX?-MthQy!t8VZ<-r&A)ju}JS=VJ;fr=lVv zPn7RST<}#aTJ_MJ-HAKPxp~abH3&^_nsB6A(6(Os%$@0ze}%MG&5shaUw5E)$3p%C z6%RdT6iriHcEG)M!<+YuD+EJdIUl*3o$$hh&#T?BL*}zi@AIW~5lgs^^`cjM`+muiI`$KY4ylI*} zv7prK*7BYMKb7Ad{K`GgVg2!Y4*yowC!bi>Z*c3YY+D7N^7=h&Gv_Id*{y>co0tB3C63sqjt4d;z2 zyLP`*-#AB}-G2I?IVN6h`Wx==iFvt?kJ*O3@Z}fwgxpdy*=+799ost2&g;6tI{Ql3 z*Q}-0?f#-qd5`9~i0w)^Q_pZpbEiSsyoQdK(usw?u6ljnb1x>O&*QmV%bD9uhqneV zH1T(jNMl?imb3idj;2~x*VQTcTYd_}zi>PA;CF&ey@t=u(*m&%T92N)v{&tl`?Mbd zWygyo^dy*_k3a0#^;I>ZQuaQ}f>XciBIZuzC=XM7vdz?BjunsB??!{WA8mY2|1q#E zyKGUta=(_wmFbUew7v*>_5EF`L7C+8bN(;md6s5q_be4PxT%%6f6ogrTh!uwKYDI%3A(*!8b+bckM;b1dc2dRm{5=CN=vz_uIot z@@rJzsF~ypNaI;luGi_Q!tJ zI(#S1f-9aw0uSSgHmQEND8}L_FiSvvamoFa&;NCQ*`{vseeDmo@HRLJN zM`mV1;ob|)ElT`J?DzQ}S2`!0iEUeH}P#V zgzOLH*Gjf=A9u*S&Hn5me^C7H44YSrSHw*H_n7;Xz^&VCPp8PtI49YbCOqFc<)YXO zuhR*UubNwb8Ch4xtdDxS&o@!>(5Lt9MF%yGJfEUacd*QNS9#dMfG4gTx$a-?`X9XU zv8cu6m~!<4j-1NR!FS&$1ar;5{Po<|gAcQv9~deADJO8;y!~WhMU_2%7o}gjv>gAn zI`h@%=gF(guXUb^_YI!^|EtK}LcfrTJ^Lpuv#WQ@pCdd~xRCFv^mCV|{Ad67b9{H6 zRety$yFTL{ac>oMn?I3{&(FD+{&Bm|kBa}HCrlsoyRKtAzS#U+=c&#a`~O%!`0oBs zw!mh8^}n@N_f~1E7gfx?S6%Wc{PXhj3)ACwI+f3sGJ5iEnf}awcMnbduvfmOKRxbl zxzDOI2h2O0)zE%E)BRE1tBv@t2iE9C~<~FYIyT0&gsHBB?J=ZSZ!bRyP zvzFNH6P|hcfA7B8`wu0=^Ri80uC;Mkwwh1!T~ta%#Ip6yUCYHZBgb?;4Z7AAIy-;m)({#s8cGtezaM_$k{l=d)Fh z-#UeOw#A3Jy$e+rz1b~jmMqM3SIsGNUfYE?Ni7>&-aX;^R;RJWB#7szor(50rMer3 z4a%e#lkYKiJo5UU)mj^s|6OU>k@k+;aV-l!XUy1rx9x@X^@B;P7w!AUIk~o9>&|T1 zryFjq_Vk}t`yb}E&F+qtxeSNg z?Hy}(t(@;A!Bt}*`t!o36+8=Hc89*aSM$%HWXE6DIYC=k4O{aSs_asqWp~W`#3hs$ z-TZ9(I+s5m(i3)8>P_KxQ?~nWr=oMq?{DVRcfMQroxS&LxK|}V#Y|j5XPM-&`Prd0FTN!-GBS!sJI z!?ykDrwn9d5NbL`E+vj%0C%1MB80Rvd`DSZy>)WN8ZviXhm&+$Eoodim$L7`EXb@Lto%AfX zC37YJsUWHRNYS39SLUDX)rginw;@z1@4{w-d1gFb#}hjwfAhD9?=YQtKaA1YFgN|8 zUdxg*?U&4?k8NgO99d>4-v5&7sMc3s7?`MIkpFS1n&)$mMs)tdv`n6qS&7Flfg&)3^Ko9DH81x45R@$E<=k zrAiu~-a`_A3(k{R|IO^kDYj9%{b%E!t0rH6nt%NtsQG{K*Z!w2bDX~(vkd$h`&s;9 z+@*-m>8;g%50d|dD_(D~7u(4u(+^5@>Pi1Ty>GZL|FitS$3O3W&R=tBuj-OkP`bLa z=X?pPpXcSC|A*x_9^5a_zh{RaEB{~S$?1A7VgDEIc=hX@#pgNP2^-rS4ch_^EmYak zzVPnPj@L!Ze%`0tuW$TZ@b-@T#9t5Co~*so+qk~Ah9fT|Cz}UeGAe@%E*^Rlct0xKJ$qetuQy|7W}RyKmQY-C&l# zyte;#x#N`?K`tUw4_;8Xd)qeaYUS#??a#dL7^v$vUQ&|=9nD*GPugJGk3@~a)tB~K zD%@Q?_1*eeOi$l1c1V2`IK;Jb|0FJrRTchkbL~QR_3M`?*Bwzdh%;eZ=E$$S>I8rH zUM+>YzXjbNyY*Jcv~B(*c(2y#(YwmtYx_B!ALd1Ll^>39`Mi0jfM2lzUv2 z+uzzs3#JwyJ|+5W`ZVR9r+*L4&DfbDeQsa5_@kng=Y!i17F=ALQTaQ|MQ`Cxk+1^? zLchtz9T9!sq?*Y%W!lYvTi=?Co(X77_i4CPs+1};#ru83rQV{iB^tKR4$j#nG40+_ z1Kz#-4>w;w6tk!KTQ|R3wd&c6-6xjTwQMfA|3~J?5fk@=mySQEdvAUBeMm6p{L^30 zeLVOu`_RVgoeNIvXYO;)pCc@Yehfqt=xSw9NB&2;(x$T?J90iB{}g`t%JchI{<|;z z(|^r=8mwA0f3AOM?~;wr)(gC|Z0!FP-x|aGFXD+*flbxpIV^P8qlta~s@tWE51*y|#7nHpUD3LQ24?j~SVLo+V)05|?CT@M` z{N?5^-Xh!f3;Xx5{r%%U;qgz#o$uaHd~Dm}Bj5e)t4G;&#hI=}{I=gW&RKu*`=t4Y zFV%L*zcT9mCi-6gUpLdwb?*yK9%+3*j|~Sag+Dxu}3@HMISi7 zt=kgwrfui%y#cqL9V)r3b42GB-{VU~S+AR%-ufIB?I?TW_~VPOL5!v3smQ+qwyy$P zHkG{p^51Ef`R&tJy}o}`mT&$zsnCG8N<^@=ZpNFbSHdlQuITMM(Ym7X`{4pNdzYnm znp-OK6^?vVJJY&5QFm3t&Mi&9N)^vMY$|l%oD%$7>D#WAFDz1ZZ=I=Q4m7@YsOou9 zgt~3}hC+YEq7%#=XSEzpEHz%R<=yhI$N7d|MO{Qg4?6tSR1FRA`rDzS*Q|M))A_n4 z$Noe;o$KeED)QAAwcEDMDE#m6XYOhN>s;AGY56UshG$nMNFU-Z$knN0?7Xs82~tUtD>*Or!P>YlVHruKKUd=O0!*|MuX+R)rt876^liiWj}b zN=oSGLxLti`W~;J;^?x?Qf^}IzleWXvwr=V{p)qW2i6DW|*T&gYUmM+<@Z82G_ew}uam*2iWud@@5@3%W@{CfN0k3YI+gl-YB zHTv~njbH4AMSoXz^msRDns9kl$V~YWEO6;v%#rvC-dlGj^crtcUvTZJX%O`$4 zQpOYUTkO;Jnw~$h@k-Zg1!_;do*2rR613x#t@n5Pqigl&|Bqc9y6gFs1>p}&r@j0A z)~3NvIsK5(RiEE{^Q(E4^6PmL%kmdo+AE$`l~eLVz^Ol4Pow$gt;kg+`EL#RH+GjF z3VGH3=mzW2cWpPr8Y7O!x!l>y*mhdo(Q>(c(lMLH3rgZVeGMh~-w$SdKKyFMpU7SB zt@w@|>{nbgA@#RbR(R)xNnBgB%9OXobMh=tSJwI~r*h}FmDAg5fuIo9Y{9LiyE5Or z?I@UbaK`h7OY6)JotJL>A=fQ%H`!36nz68o|nR`@DqWdq$!nZudRS9V?_g(#&ard2%&6hh; zKkRaLnah8)jCp@YZqAEm<}&5A7HZIXZ2h^;AMec_1^z`L&)0&70xbTW`Y%YJBnpR? zMExA!n`g%^`dwAPB>!_i*G{%S8&8-%sQ7ZsGSE`+^YnW5KmS1G-t+SF|5K*Dvy|1Z z_bb2Mw7Kwm|1P8DPg2eWzWKL_PrK~R!A;`vt{*3#E-ZKC{MEl(^t$Tug{M=)7x=#3 z#=*s0_1ninhyq&~UJ?DLr^3>nodB%7C!uRLiPk3(G zQ}<8m$+YW>`uB@P?z2h>h&b@v>&L8~^P(S2E$y!cFIVe*T5MXs#_FGcQN2Rddec4s z+z%CV#$`R+`R`BFEb9pmEI<3QiR4N%uU~m_+h)gI{>xITR?5DeY+`?*{`vJ6Hf4M| zhPqqdzpuQm@aUVmK;GxWFF2;XJDsTeWzo?)vIg;Hp;O;EPZC$iJIN<8H|FYH-sZdP zo7Zv$ZoA8|J6V<|R^7qUTz;eQJhu$PwdoJ{wXDAJdttzTF?ZHS+;=-<&rN-I{IJan zK@fV!KUoZXWve@AQFEyn}%NO6g)4Syye|Di|uj}6%k>Bn&o)F^i z`RTo|+;r-WO>W_p6`mpB!fGWTR-2YGf5BE@1x-HXx!mM7W-qg4DpYPxL zkW>FN{w7-rJ`Vu(6-!{rj_bSjGMCruxcwd^|Ht2sdUL5ZXZ5^Yx5egkaxcC0_rC4a z`C-Z5#Z4<}&$CzjGZmi~@z<(LzK-v(nvY|Wo0;B=8cCs9TOBhpOlsF1S|E`rc~xwg zS-SG4H`*7rJy$4^sW9Jl_od6EV$~|!t-O1~-@47ee$PGr`t}oREt=MRl1iC&U3oK; z$5-Z)a$=WXPk6P?an@^%OR-l^6kJ#OE52x#nAqjWIq4VYUf=v;-m6uis^3(+HZ2!B zsk7nttBG%zC+(S26}8+o_WNq1dWT=j4TB;ZmS4a0qH@2WdoCLdS3I+*CHCrzcgzXe9Q(Kf%Em(FtOTwyu@jo`@sz=2f8-K2t@1@b# z%KFDkrYD{#J9O*6ol8%R)I;GohbvP;J7#ZXG%SvHzw$@7L$5%wYn$e&jnaZkcPB3W zKI?}Y!*e+anNSn=TWquDpLD$KTWqq*ob$()S6^-9j{CjN_+Q{!w|MR~@!;Kyyk=;$ zCEZH@wwJMSKi8?+n49&|LSE*||E?uTM3uPxHr|zVcG}%9KkZlj-kb3TdwOmCzx%Dr znMi?_{c`9xg@7vAmj8T8;I5+mp}p@JUCV0=nB;361zPs=&aHm4|KD|?j7u+G&iV1q zwrUaNp2$o+uUpTr-QM@W=I^cYT|vvAyq|3we=Yg6z5U@&xuF#`U&YR7?BI7+yVLq^ z7s>6nWRD2N=cf_hECDJ-@i~ zUzWAlyVC`8O4fINs|j{I{J7zU|KrR{b-mx_OnPVQwEp^n?eZ_SKV{I9Fynrk{Kb%k0`Ysx5U~tFLIi>pIMz>?!KRU6Wz^w{*ttdwffM&2;|ec5KaUySh>KT2g=5 z+q)nAP0IImzUb6Ww8%F}wXm&Y4148YxZ%LLtM4@ZKo{m}mno(^nH+FVo;7&+#f}}H z_*&xXqQ2BpXoj>8S+y3*H(9E+~QOYzgAum%6RX|nx>;+jHi># zkG2}j@@~1@z9C57X?oGrD&1?7yk=xt_guMmAoS1MP!X|rTMI&~gwLKnacJ*dcTSFP zU*>#wbdES?e&Jie6o(MU`;iOZsSAHiUfOc;#=_Gv9OXGjB$sAi%saE!VB4Ff%jfs9 zN#D(1{AzR7-S-a3=Pu9QciDFOf2GdfcV4+VfCl6LD-V_rP$+i!SMl&!!j)3QlEcHthK~yTkR5hQ`LXg4gy5Khl5K_wA%}!MzJJ zcO?BXx)bzA>5|<2rhV)8b=RfdUz}el`zxGrr>Mg;`4c+nk$d?%^TZyP9;qz1y%amg z>dT_zGxHQ|KS-_j{_G(7L3&B~+r7M(YIHBHxUUkgTlM4hl`mg5R;&-baDK@bt<~;f zl4n~dNeaBaoWUdZQaYtd&Rz8F)#Hv2zixSA_D7EIe0OTywC_8M{+&1HmRVSNZ4rO& z_lDg2i$1^Dx#7sR1-0odMU_&A()aZk%$8sF=TDE$<1+iDbi%$0AaPcxPe9+#gI%A(eAi?_*!`TS)%v zTi2GgJlCSp^-#&dr&PiF)z0{-8Q zyc8U^@b^%sYfH6f^4a-<(8MD*@!f2`*h$~QU*;wkh}o4nguk2O(dYX#{pyXsuDXBZ z<&U*}`dg8IU+#M-2AtVwaAb>f#BS~h z1!3k}c4@Ib=h-DZ-LIY_Tzu^@-TwD3x4O(PreAh{@&2D2vsU)R?d>IhXu=J@HlZ zER}HEs&|jFK7Tz?!npTk<$I&5HDUA8H_Dy}NGAt& z$QJ&N_hP%n1&i-H+*l&oF?SQ&#>>}~v)-k-=-=z?IJ#S4*AegccbT(K+9zt><$iog zYb}3WeW(1gOI#8;aT>095{1TeAyLt7k&t)Gb9sPa4N-A9VHFM#m;?lZ5 zcJq|`7jAv-^?ll;q89zH%A1ZfXWSCscuuxM_fF3Z*Vh8Z-&K#C`Nyub+^%VYr?;Tp zDv66bjRmJxXe;e{s_^E|I}XdwMiR5jTMi0!_dSno(%o&p;C#{LN$-BftS#r`=bQhT zd3ilJ>GWeyI?j-!11onRNvCSx_2_)*=Hkp>)fKtt`*%%R?(%7V&__*u{^ZlM=O=tU zx3{8hF}L^psK&zJcL#kvBX_*mlzs8x8Eyf&yF6X0P5)+}Ty;#~E>q%KYo%3>mv-#e z;at9i=LqZJ_s2{>%Q<}#Ucd1Ath+D%9&0~QR@;5&lhl(Cr?p)7K|2Z}6V^LVTiq7a zeP)wXO2CdFTkr2H^?Ym6Gv{4axM<$-VQ(2@q;=!HEh&QY;;(Bx&Yt>J*mB>m`Ii)4 zY>t_~sOwj$$G5AOlKptEaQ({qTD5l8-l)6(mS-fJxvZVEe%so>+u!xrf93z>n%j1^ zL3Nd-NHgoQ)xQwm5T0JMa;2IDA{GQb8hOlpjW$7_<8zl zIkvxbOi5II^mdDmp8JZuGKs2FISQv8j@l%BY|;Unh*|O5q}$jE<3u0r{A6Q)UF+rQ zCR^8y!PZ?9w;jxR9&#Y$P4mVR&ffaA-!FcvOxUt}rTslS`(EeVX<>(I{>w$k-{{`J zUZ%X}O813Fu^k$B+FrOy3!1-HHQD{yP^D+H@}~DCE=&4dU-xbnhSY5=ahk_5n&S2)LaaO+;C-{ZS)cel`Y+x6S$9+b5E92|QwSm~$o zMXiU|6JPJQJN_#6{}Fq$T>&3Xe3r1QWUET~dc5@H-V1L}y}q!hvU|R&Su>}X2g{kl zwSt9J>PJd^yKvTZsWhM>inhJd(-qUX1*|q zP?~-D--bgqx$!M$<5qiqKN@OqW9K3p?swJ#`?m0K8k--eI9ej1KZhlFyLIBuG$|)x zaRrs79M?>w5B;)jxbV-h2;CA`JjPDJrk7@h)?g*cg@3e70 zXYs|j7Lm7ZGjh#)T4P%60&?S~KHR(INyehR3tO~rPd}Wocj2@ra2#Zo=cu%s9L_dVG+mdi`I`{ zCY}>?jSXZmQ=edcey*Wh(~Nh1f4zVGeQh4Bx_|A@u=3LKQv0iS-+zDoweswl&mrOE zq2b}z*T24OdbMVC{reF6^Gkov)n0u+e(L(VdT-OeXRmJgc|Q8mzgLDbtDcodmCC1w zoGv?NYQAjq`kjh%%dgJ9ymS4}yCui8>y_vJTf2Y8xqrLlz0zxLhQ8c#zCP9J%b$Et z_IbaiUj4MsK-Qur^Va_j{=U`k|95P8{-=GJZMwaIti^N2%>OH&FWXz~xAI!@R^O-p zGZXJ;X5Mc!zMQ#y?p!`O3)8pvx9h*P-J);%ZL_`c-jA~HeYWra9DnF-MbF9KFEZ}e z_J{ob_1L_AU-)b4_J93fg7;TC|2h`F$IA9A>-^bvpEoUE$}Ru<#+L^E z{~cTYA6WQm#w&-Z!I#hNE55vMv)mNF+5^g$Udn%MDfy;uZ(93g^MB{6Kjtwr?S5ai zpJ(^i{r(iYzhB)i2j~C!{bi2zf7RM^vG4uQ?|WSP^5*va`nG?seP6cx-j{n{rbrjx zi+{@e^5jmP`Q1N4{bHut{kMr<`mgr*`iS}KW9mKsK3}@MMjiWyb$2e)(nn&!r{5>Z@w4UZ?J#d#>`R^ySF)HOW?gP4hjMhh3K4^X*y5 ztL%L5_M(@6O}B^a)P1}E(YeRr^UjAau#f9HAHI0~zuG_L>-Sm8u32`s`s>-NfA3By zi>pqaE4`|Izvp2-v%+lCw{Krw5Y1oxucq2`e|gCEO7~kkm!0}}srGBc z`dv$BZQFI;=5ML_lE2sYJ^eAiec$tGCAYrco9DOt-L;ZeQ}cb7?_X7Q=Ki^EspOPu{G4y~pDthi*Z=RymiLF{Cw{Ygu=w)1TKQ>Z=c)s4 zl|N&?yfWQRVQ$f_zn9LeSMgv)Av@+xzp~e#^{i+3ZjIEN`W6pZxvn z*F5=#qZ)UaK}nRsK&Q1@GP_OZ&U2UT+QIu(7`C-t`ul6=he^BlYerowi~An^UtpS@ zYul;0^C^)^8RntFQeE0+V|y<{=ZXSo}}+luzklVzP#P` zO@Ygr&W%>Bt%L~kMbI$#k;lAv${o9o<=cNDt{bgeO&r@GkO#hou z^`~_IlzG3i%a@A({};dH{O*tWm!9AKb^hhi@;&;t@3-+US$_Y|zb{jw{X`!4PkCS; z@O1CXNw@D!@4NF$KlEPs-`}v&k_TP4FW;M!oo8Ry`g3k}-s*pQbY&~fzI{36 z`kje>dmr_cczxZ?eEsgUZ}HXMR!?t5Up|j z^Zvurm+rOO*vVFXNWQ%9cWulq%g=`|-3xv{_nWNE-l~7!m+ZB!zP!)&_vM4k%a<_! z=3lz`yp{Ug(qlU_>%V@l{mr**^EoSh+h@Y_C(QfMa^7#c%}>jho6>FUYtL@qV^jO~ zblk*!&+O~7fAy{Y@BM4x{My=INAK^~{d;Wff9<_L`|nTA`+K^cZ~OU;s_!Q+uKjv) z=kM9C?%FbR2k&ABC)ETCuJ1)@63x{Ru{>T!@KnN*OG@5lefE{j(*mu|8Mgb z_WD2mi9dr=UflV&|Nqk;S3l(c+x&m_y)W+dbJx}1j$bza@7(B1Q|*6jetCNTzuT8C z&->2$a1|A1STPuFaDeusStGk;Bm)t8I&r`UZf z-lt>x>-0WT+jq0~o5z+s)nB4)UzS{TP5Pgb?fyfppUovD|C^G~b( z`Mym3-VgtmC%5n2e~SI`>A>izbAuT6>jaIAcp`<@rC zFEw61=Wm^UPkrv!JL~<^_q_Uk`JZ;4fAy+2G11fCt>0_>Hl{fI>-X(T_lEDceY^8n z?$z_F>f@{3zka^GYX03>W$X6*toT@azVv7M)%WYG+;3GMm0!vn{-rbWm`Vq()xKm`~QMj-#e53{(Bt0Z~yBbQ{Q}luKg~)H2GHf(df$u zwewaL6d&5iIpzIc)4AVnZC_>_|C{~gp6h?Yte$_G@BjVR zEqUMXzb?J^e7^UC_2r%Z|IfdCqZ~i^+|M`qp3Cju6<^xdUQ=|d`djnmb1%QIo_G9n zU;N5{SDe$A7oXpmZ}s#*Waj;I3rh;_zkGS2G5FHFz15fQ?XAAN(0OM}eb}z|f8WiQ z`|H{7>%ZsArY~Eb?=!G{TpTxN->0SbXVpDeUT^#NO8y?pzt7J8*ZzA?{-5ivhcE6g z+b;Ke_m^qA|9|`Ot@Qcsd$ZqFegA!VZvEGNk8j6rvF+2XjGy!UrcX@Q{h8NaM96Oul^oKP;ew~V@1w(rO6`;uY~u3-~z->+OGP;EkKa_0QsLtl=Zo;Ovl@ZjRhJJaV({`U6G>b}F@!YyZf zn^Sjx%lkR(OaICjCR+V4oImYc<)Pxs?sey`FL$pyd-Wyf`hTroru?sSuDZAWPhi!# z{HIF){(H}_KF)rb`~9DUs&l#Teb2A?G-dWb%hF`ymt9L?YDQ{E53YFUtMYS`f9%C?{zzr z-^NT_?-u`eX7~49&P!4u;07-eSPAs>U-|XYoFWN$W|RV9#&shajW>$ z&CK-kjU_J*G?u(-{G0jzoS)U}2aP4`FZ+A*@A_VQ>9F0OgD(Z;%PnmGEjsVxUv+Eq zrPTA4j=$#U|G4y}HU8(-c}g zyMCVkZ@BmAi~CE>@BCZ-@^}3+^ULOY-<BN_OySFP}CeQ!a`tsrV|GU0$>i^yM<>mjc z<(D_t|Eymk9{2zGOYZ!izrQS+{a33j z{(U(k9b6SQb@}!1?dMy6dasTty4<_UE7ewdt?9`Zs;wzy8b3dVlw9`99_I<#Wt()6V_+lkcCt=h?9>?@w7@ zy0`k&rP`^l-dBH~`uua~ht>Aw>&}HAJg@pIYgcx@Z}s|ps&DJho(;|Ot^V|W-I?t> zS9=9tSf5w?N9)J-<$FW>;=le|Rr9+>UcGKU_wV*qx93&8&3*ah{a)R2H{nPKMnz4BAa z&h3o9RerAa^1Z*ym+!6STWb58FXa63eOum7aejHBGJU!2a+x*rrZ;Eamt4NgIrNCD`@iqk>-~KqeQ#3T<7xk=*1fOaV|@3A z`u_=ezc&A`u8Gd*`dWEAW8adEnUyD}zUTXXR{i-4?N@iL84d?R(vv{_iz=nBDhvz; z=HPNHa>x13XTi(rIw9p)*+JFv|886EyxsHW_dVfhj;>|q@>-GiKRy3Hqwd@Ge9!BD zw$Jz1uRR+2(tghu(U+F4|G~=4=U+BoI(PHC zzqNdsqty@2`4i=S+|>7&|KZW}CH|E!OD;y7t9QqKx1Zj_=kx8q9ur>rH~r4cOU3n{GpcS}{ug_z{KK9t#sAxv8r%K1zMQ#! zpXJ-#ullxZ*E}k(e=L0EzrFuYMPE9oooD&)`9AG@yMNE`y*fWPJ8#{x{id>2XLVm* zn7V!0-klxse{-JiioQHAyZGS0b+6uA{>=Jb9`b+R;~%wecK`aeW&f|62jA~ho?CkN z>`S}#mC02HPSr0IHf4F^F^L&-3Rl&#UOKtnB<8J+IQC~Wj-B#=E zobL~|6kq#$>0IXXWqZ@-N3XqYeCgah=1l*o&DH$N{$91ZzkT!MZ(%n#r>o0alql5R zaF&}R_w(fZIrIM9dA?k{{%F0;y!HR4<@^7CxqH2@{g>|jQ|rFx)=$g(eY)Of`~T3` zW#^av`cspiv2mYv<$dY5lW*^QaAM>2>$^)C+918y+X}xPZ1uF|WMDYs`Y`U;tW@!L z#fvJZu8db>h<1Kiw^w%Gj~nhW1t%|lcR79UTz1`?{KG*jPDo7dzGW`+d-oS+`~PiU zF1_DlReRH0&;R_7JNh2;fBf+G<^TV~-{ZOcgMXRl8=EuJ{q-KbUti>P%d+I&N2Z@E z`F}1c+3#N!^=rlHeLAxLy59Ts*MB{~Tz%iWwJ$Z-+s~T!tNOj4|Nf8vmx$Yn#}*lR^?57zBRFfiGkt&7f%<*kUKyAUoy`7^StCH@Bc~jijs?`{w~>5GXL|H zWfk={?{}xaw2Qa=-!~=h$L;!AcK>$%SA1(L(`p-zxs#U{!Ey zVabax!I$Ult-gGax&4ynZ-39_@;1t~e~#N2)gDWanWR_!>-iG#+K2o7V7S3{v3z5O764U7|bnw_cQbS zz2>VMW6o)2s>kQnz2BMnJdWR+-R@WLrJeoNQNPU3|C#jVf__D2)sgEp=~ahT$4#I2 zO*r3oe*NG0CHi|mzQ6qZ?vMIQ&+mNbe`#sHU*GoeGyY|#^E_Xk4At9d`Mmk%rN>Wy z^DkL`Z^q|;U$*?>`Z4`(#NU0VjNf^^EWTGz{%gi7yKk~q_Od^2_u1*2oh9?v`^;ZoQ~xwKGk#y!->c?J=Vs@7RIl6n@yFG7{qchK@dy9Cvc9}; z_5HZNYj(X2HD4CI|M|0$V_Wn6()YhAzf>Fl_4dnu>#J&aoj+Y?Hurz%{mJL54nBR^ zQhNrT?_UpTg8QPYE2di!? z{Q0oe51h?iK-oN06qL=Ut&Gbm(Tz8cJ`&! z`~Dxle0tr_#xJVzzvo|0Jzp1C_2YQ_jB|VMGhaTpFZj~A#>=Z7?{-!%da-6pvDZ}N zbN=jdUkYFDoBwUkmyPrP=e`u)|C9Z)@VY-wOP(Ch^E?0h?S3EqT@U{+eSZJbzAtm4 z?I+ADe9eA2_3C{8ouE?Re80Wz&t)~DfB2W2US;#NZO)W=CCOgrYpT7@|F~Mdgx&UI zYv%dJ;>()zKkO)(zy9~Xl3$@aP3qHc6`y@u`tskpEzeJZdPC2W{^n*rzcu^vx$k!- zl*Ls$->Sdr`|^S5_GQf1@6O|kDb2QezBS)JJ+?5|>PwgTlH$03*)QMZ|JYb^Onk4_ z+~0rlJ)YbC5W$*re*z)|!@1?fC&-?xUHh12)cc;Tw+pE64TfO1e z?|)mqKlu0RJNu=1ug?Ev=U>ME`vd#(cDp~xm(=S{Enm9a_WRM7+WQ_xU*5j&SM}xk zf9I|*d;kCC_2u{fAKvd%zV~F>^j?XdqP`!!Fd;VHB=C{V`` zy&TjUtP0*SePi0x*57k7_NBg>&U4_}jVXtIJ?!p}y)w0GQg8XeiOKd-w*7(N{C;|o z((5@jU%$S7|LgOY`1)t&m$t`#{rl3s=CSsrV!QX=m(y*3&U|@T?$3vkXE&cOwYC23 zA0t@*er0C*eap<}XZXFXzyIG+@*r{kly}7!H(xp&|H1U-hx(e*UuRa=>Dm5O&+|C{ z_v`M<-1k56zii#U$KLk!vG2>$@6EY%InVRuO7qbAu$^z#YRc|ZT)KR}?NaahkLyb2 z`&XCUvMi{x`uib$$?ttn+AppA$@TsEp1r5!m(A6F?`0jnfAO#1@}c$h(YJQK`d0Fy z`n?bP`r2%($FuA`zSn(yzpQ%ipYY2+?|<6-vhM%OXoaetP^&y1_Q%fB?c?#c5@{rmr&eOZ40oA}GV@_+T9 zm`d37IrcsO_0?a_XRoTw-g3To()XKR-$^$-g*CQnIc_fjM^_*yx~_tv>t5=s>AVN7 z6-@ncw&>x+#rvgfrw7J;t9{Suy;y1Xr0sj3H^1cn_hS9!_4j_*U!MB^?ek0Kb9yoL?1R^^RBG=ll-UrO)>$ zFIE44DE8&O`QPqrDSoi;<(%U-s)TfzBG8;MRcPajT6@7o=y{hl`mu%mE{OarJ z6ZiP{e%qS3s=erFyTAMm6Z|}Z8A3Ua1_pY~m$@~8|&6kAN-tS+& z{^zssWy@`zyS^0c|2d`P-ro1q{cOK}*^>YM->Vq~{D2EUq1KO-tO(3zYnket&O-~HM`$>x#Rmv9vskod1muD zPj-G;uj=n_{g=(!TIuNt|(c53szH{I>`4UJ0qx!$R+8Woq%OwS7k~XL@g?qmj)yN_{_lkJ<<0dkcwZ`t@3(ks`FY{XxcOhc ze7T@sA8Pe~b=>T8f12|B({0~sU*73oQ)u;g;{3UMHa`SkUTC+~dAs-feoyP=^J*Wx z`n5Bm{=pTkndhD;BlAE9Z<^Jl* zuc_Sisl8t9@AdgV&whDr{x`7dwEX|GAB+Cp{`ZUdxUlYO9reolGrupqxX<*|$J*kp z{p-rFGw95}WXr(7kkkBv`KB7Et9HTjVcfAcVFE7t8imW=JUK3;Wch2R@|1R?{v#p-} z=-$CC#qXLg)yn_8`BK^bvE<7+?J|DVezx{=KP~9@Ot*d2nfc$nKI+!a`^T5gHMZAz zJEzF+*3I4aZ!IglZr$Acy<(<){MLV8HlOo+e(yWu%YX78C%!x=|CjUSr1ZTCdp~sV zH>y2+dY#Ap-;?*3{o!V`|8#o){JLkqqA#z%|L6G2Wv>kH{ap9G^|Zoj?|bRbYSXv) z^9H}2{CVHcBXLp)w5RdB6Vi_ND2yANPCDuY0-l zrLx^8#g}&9cllfW?kQfKeBzhakLu#GOXm*$&3te9-K*N)QvdDU>L{y{4?QKfW~MLu zELX7q%LIRmIdb1P#g{1iSNr{HTL0tMm#6Xdl~pHC$4`#?+-yI0-*feP-M^or{~7#! zcl)0bsO_eI_v5>cZS_3e%am@< zxqbhq{>!z;|Jm*Rcg^0EP6+aGx z2Fe@0G_coK{MzCFXGzJ!<#l0IXI#aXpRYKz`BL})zt@-L*IeED@|OJXr(gcs|DOF) z{C-Jz)#Ka$jrM-+|F3rU*Yp1~?){s`zs&ld*U7pwCx0K)|8@R7&-b~pmizeEeLMO! zPqN`EytJ@@loszCLFs&>$h+c2zjHqQIR4kmg6;KOW%u>#dlp`z=d@1uZ9T|NE8w(%bs4_dw&iW`7UOe&=QXM|=P5b+vc( zmrRfSTl;eA{ol$jf7iV;ztsQd*8AoE|4HY2+JD`>ed+zm+wsey|9_gl`nKlv^~s;? z4BtyHu1!l{Rhxg0{rc`?hA@9c*0T~c>zjY>sQmxfhd;8forw;x zt@*gIyDvVq-u2}7@cUmCuKoR(e(|r4w%q@ded7C@e@D4Z3si}Je!TwAM)u#E=cKOD zE<4Za-uE}-i;e#&Z?oW79n{ZFu?e_`K zlfUm&edj*#4wn3DJs?5Y2W}VUwEmv+Vqfv1jo0_gTt1;@&lb5e*^!dFO!mI(x9@(J z9vSI2(Q?=P)KtxWd+}4vzoWcX{a*FH%;5E(A5-6L_ul%q#{El-|LO3(pJHF~mj6wT z@DLTedE`lL+`aFvzmKe3`EBdhJn@FRu%UxL3$}s#6dHF`qTiyRXnJFGq^u_jKv7K)G-`MwF`^!%M=jL9eq%?cN zp8nsj-p^T|Q(tpG>TUAtySfa!VS)dpF&CT(w#@-&0^K{$UHTt>?EgKr;Eiedly}qR zmj0gX`hD{njl1)6)_Q*zj@$3`efH|Ad-ji-yx;xneNp*-a{d2L`@Q!6xb|OKEX>vA znqlSqY2R}%+O7BhWM}$5`t@BMhQ|w_eWhO)wt%a~T&^+w{vkvD*6KViJ+>qC{9p6C z$<{NB{eSJT_mBU5_r2f!@2BhM*ZuGRr>V22t7DB(W&D)yo|X5fd=I?1FZp^zM11;QMbA7X6M$d$n8Er$qj=Pwn>R&UeW(SH3R`I6cq)^}{Cb z|9=w2%U;KT0Pvx)ae_g9iPJQoZ|L67p33ad6)$?#~R8n$Qd!PNuPW^rLCp(Q> z=4ajE#<6rYr5U}%VJm460qR3>z5+&LYw z*$hm3s`@F2~CewjiyB>Jfe_F$>XIYm1#cq3s-OJ4SN%MZG)=&I*cl)2v zU(??IeEr4N{(t#P_xm6CUoNkI|9?sRzK{Qxn&0`^e|)`H?dAD@izAkaPJHWgPyI=) z^F8+`wXXNlpVYeFYkyMfd2jiXTJL+uU4GAVo&3GC@_xj7HO2^jukSjmR+rcx5q-J% z=Q9Qd27}cvobQ5L5jmWo0pDwD3+4;kZky9>|8Mu9kJryw7JZ4B`nvelbOVMpYY+SX znk*jjKGVGIeWj98^u)#HcAIXAU)$}h%->n|x z&%|&*ECAHZljZ(ydGT>JwBVj?{`Is3!?n1t`@Z#X_sLWz|K988k`i2eaN^?qpPF4_?z3e1J5lg+G#iIy#Ungi|u>BgHbmMK;`V3?1y&hRXwSE@;iTj z%8)Vhe>L5RA*Y9XUdfwD+r8axU1tI+Nq;jYZ%0%k2I4;plNatAB@B8*~dFrtf{sc=h*# z@c4=+H+a?ens;@qiSCm*`+L*+x+h2Li~d)cvmZzc*p;sF_N|6`JY#)eZ0o-o(HaH@ zhF?}Mo$rH(*mE?%A-)$B;@6D6p8l{SW}kiB0sEpazp8sqf7(CwEu&lVrRhsQy?=Ew zVv@Vf%NwoYF;#D-ipyAjQ@>cOpk#FHNo^RoxY%pWobXxWuJM$Z4f*-rmffeG{>s?r z9OEj_z>rYwBF|(F>fa={-9^-g##9^|WAv>7o+*xc)s~`sGU>PE3w3KB#)U?$IG-_x#-tPF$?- znWru|QL(rte)4zA^A?s&eb>H2sn+Da4A>rpN!;H986#5W z8htxd>-S~H=_~r44A(j?{jJ*hVbcG<58LOLeCZUod-)j5c-}mo`-IE)p zmGg^g?iZcB$iKeaoMA!9`3bjf2b~iLUlRW;^a~@%H3^^hym;fj-(HKMdyV(Cg&_Za zGy3W+1oAIG0|U#HcalMRH@0tIKF9TGQLV{+#dVALLHZbGt84x5TH}3fDM-sdbS>9K zU$$!;cmI4AYzf1yMW5E6uu7Q=>KqwOSs6bG6kF8{3gs z`T;iFK@()S^EFVDL)UrcFICP1QsC;J$?H3hE+`L$pyVM2hK933Q|n!?DF$w=J<~aD zq0iq*ED62fs)liCb=$R4*I$Y*pF=@iHU@@fxE%eqT{1)l!M*k=iM$u!UXyOlR-cWygwgw^D4h}EprDo=bTmIzw(fvy6%k$L_z;-c2hWyhNp1ROAY+^>WjY9F(>GPwPB& zL20$e7Tal91Antl?9$(gr#@b+DFy`vLxT=sEk18^wV!_t6}uYxqP|8HtWALkT{{!* z-`iItHudz^i#6>#y6nIj*X&>MCt6L*xqFS$#cvU6THE?9FQOz@fvNBQhfh5(_;O-q z__X&R?;)iW0%6(V^<8Q66aG~`LU+}mg=l6x3)tRWw>GzeFRHr{!e48h*um9O>W(Ed_ z=#ohrz>P;~75o7|zwNMae*RW=?j%aaGRHq zM;Laih`%`S+8e-gve*Rme1+wu# z+G*7vFN_bttDkM*A=~mHP3TT+HS(*cJI+9Y_rSAmJLxmw2jWA*7uv_Og4(Ip1hQyc z-|3!15TEV?`BWa&r$jh>-TFlZ;Fj3!g|EB5I8S*D=>z2~CzvQ+#Kf^Ss9M!Ld3Sp8 z`3ps$7C8e0L&DTW=ea(Meh|MJ`r>@}K~Sg1?xN=*eC14BpR?OV;RIH2U`s3n`2#J+ zo`bp>ue?CBu-GeyRns4T4ZWql=2_=P=>uKh_L_kcsL8^>z+kWiYv~r(*FEjs=eLXz zil8ct!A)cOgT?3hky@J#XT`7;<5y1`#xLAgyJ=Ov$UDZeM)0^ZDB>9oh+%DUy?VOC z@B8$(OdY8~an)>oY{=6k*PSN*`F(6j=INIV5klZV1Z!>36_{Fo zdQ10()jD^dKUsZW`+j~pa&t2GePsAVyZEbiT9^E_?lEsjfyNU^b2O;Ul_|b3Ja$+9 zqKf^s$c>1tbIotv{=el?+P~W0W|r&9__69NUtKbPamD^v6rH{1pF-cxe!DL}aq}Ku zVTNfwp!yGF9|J=IH>kz(R_ROLw0FfK@3*TX2hXr>bFu6YdFkZ~@s zPDrGb307_Q{w{9+Y+k>+nK`Q7V{1}>2km>Sw4FVH4T~SkcbCk6ob~-Sa+^8iwi!dV z8y45;Ed6bh`}>=3QK4P>JfZ(fex7=U+{C-OjrYJTa1{b}8#HIvCzEi(AP?SMz33~D_6ReuK^mn(s@OkyFOMjoC zq_?uYJUe6RFTGywrT3V5g94Vc_BvwM`OD8L%g@^Xw|oA5eP_OXiJI-aKbu4Crg@(1 zSG1q@Y2(v1uQs|Ob)uqA;?)za!<|8?CRBN9xIC)#?TR6Z-0TK3+C{1KW(r2=}8je?eJzamoDd+ZW%e ztIqO2C&2pMsC&w(=aK6?XMXKw_&nuZ`=3vT7onsKyXn3k*ln&>i_kZ%_01rs>{w-#>fWr+;mJ zNWH#QUw3NlOt@dCC+f`j44yZmwNM)3Te>goj@{M2?#sRH1@o6@i(e~t+;`sSY*Fja z7vBOGeX6;C{PJ^ehT8e7BbVt4+x}A&Mh@h;>wa+DZcQzW;*}OZ;xMAhyJuv{hImoui~?bmzN*7pT2X^?Wr@DmdO9DiTbss-xaw- zo;7z})IO$Sz5Cj&kRCq+!x<%5d*f$VSX1w*{i(NHU)#q8Kq1QX#MYpGS=3QRm zA84n(D|brf>#C@id*2tEzAu)#`+NdQJoJ)SD?-z^ShP|{~{x@RQnNKmU+wY^BvMAer+of7zkjKtEe7)Lbe(qW=@F>**Gq{n7 zCl=MtoZ1CWu^CH$SL|H$GHmL%;7}CU2i3j^U|xG62eD8 z3o{w6O#~N*s|BH*xSZ9gqHj-Y-2Luz|I%M0dFPpSOMlOKu?;jUv_nYOdFHE~60`T% zTV+o@5NKz9iN&Z zc`D@mQHj#pn;alBj(LD8@`kIh;MsQ5sQdfwqOUI?2_bI#rCPs|+Y61iX}^E2akpO^ zG)6S9dt;XP`|yAEMIaL#{Jp;G1lQGl{yb;9F#|)wZCH2Uc3$Ms-?5sT+`uvaWro-H zTKUsj>-6q#FPSfVw@xx}mwY&A=+0I=`P!TBWlv8#f{eQ``I7DFD>mmrUTe5y%i!RD zTI1gPE{JpFf?w86Tl72ND5wtpZ=?QSZ`b>>wkZ8dyUEY4e&5-ZXWfR9uJk}Ag0@R; z)>*&)62i~}JZ(Dn-ru^qc=4zFnPq~z@+VrSuPFMtmA)mAKcVyQ)%gxj7 zE`Hj(Ec?=TZwAZFb%F}^)4u&tU;B99`OuQvhA2&=;*EWJtH52~WbiO?>u(uQ1H$2@ z976)1v&j2$Ur0;$yKd2QAFUStnUQkAlWS(LXqaC=t=@d4&Ha78Gb8u2Ot@?_?Zw9@ z_kF8uewM$zZE|vdciv?`Ow00a{kD4wPK0Hk$$>7e@0K8MfWrI0oOSCj{ku>)?aeGm zn7@mDa&Be_hrHG1n8dn^F5lzR<~{nmCvaE%e9$OwmEN_pp0|TUAxXMy@+Dj6ms&_6 zQylTd>-*1dyVbP*PsS;XA6AuywS_-U+?^f|S_Biw z7hiKmd?Et_XqtYWq7SmU#!*`e=5Jozx(4D%x#E|0={Jx5j#<|K&ivG-?(db6i+|@X zDzRS&b)@h0{~tE4j#pudSo(PB?+3wB8K3`O}yWpP9 zz88ki?(6>A^X+Eaw2gdfy5WAC-?c}$F)$=>{M3qx1q~uTXi?<)Zn;|v>aDn4=Of*2 z)=o^FnL78Yr{3-Fn*X)oxutYT1^?l~O(ndVpwZzTzxjtsCWrPM{aowv{jQ&0$a$?< z{!=nPCC^FSYkIQ3@?PnU@Bh4hn#Hz$(fDk$ZQuFpIy1jnq(v?(dlvhj0R*&-R~F2# zzrKCK{@1Bc|Hrjos-5h5`uom1c2|s^9N(1jb6@_=eVv#7LQ9Sa_n)V4s^-*~&%RbDT-2M7$()=lZJ}*m|FMKwAg-QSN*BhTq z=}(`w=&zb8a)0dezIAGEg&R-aQlIhM((|kyxT7gC*Kpdq-_us7Yj*GlKAFG$#J#DJ z*V6Al*F0Z*@%YW>pQkBue|YQiI`zeGaORL$`hxlFoD~tKYb*A@24yxZvkvQ4uY2_O zYiPq{U#pE)r#Z{YW`c^@124`5?UJ_&+_~%Ga*KP*W#)a=&GG-$lL%@VdcIz8<$I;n zt3to3nA+>l_hjDxZuAaZ7bTQD)0|c&`rbY82&lrqGE=eh+^4z?d*`1%`P<=v^RGD} z@2Z<-#e%!V23EO=}L-PPQw1T=`1K*E;3aZC8`Ci@;4EiMcBr z?mzcBck%6%cdly4rNUS5qPtta7h8ILFL1IucP zk$2rMymJ5hUnl#OqvqB8&OCiFUGiDwyiYZ!pPuT^pZ2rF*8bua=Vg2D+x44A)t}u| zb{Dkxh#_IhsnvJCYu+)xb@X>2)^UMfsypA^`fb@7Y@s+`LN{Kz_R@0})7{{nX+lYw zW+MOGbwTBe-zU#r@;;sCVshk@ZMm1bzE?)7&-_|B>x-r5{in0O&5*i0O?UbGJ-3#f z-TnT#K;j36`q6$jB*d`%nE4_al^H9$3MYWOXuh{vM@4Qh0m4^)%XKp=X zoARsY@#5QCjaOdS>!DaP@wVHd+vYRFQ)N z+aL=W*7U}>gzgfbISHw8bw%GZq4>+rQ&E*h`?9Ot;2taHpKG`Dw_X4G)h>6RZ*H6O zqVUV!w>ryoPcKQTy8nD-{=z-)|NNQ})^mR5mYm67W|q%?3$9EPOlBq3{A(y)%pHtl z|3&c(GoSmj zxmN^jOladX@qPc+6o96?PWnzez5M$1`a{c3sXH&9Ycsbj$ZXf&J?ce~Mvwr08T~BI z?boy=zeU!6Hp#tM^0vgbE#p?%v!8zwZ*3}h`P=5})all{YOQPMEw(#{<5CEUWukYEPuth}d(JP_ohuhz`g^QyX6IUyw<&Mds3$EJK7Ud4-HlXG zO~x=qT@snn2Y2AIUv1HS`U-K@^eeRM|(o{ZC zc$W@%1n$6#FS^Cg-;@Ag}&(YsqVt?iH z#q>y!CmGnxR~FO?H|&rj>! zO?S3cKPzqwwlORzoBMlY2$LRSs&wkz;{vU}{VMCF-li9u)y1E7ds!T0Vr6N4-t)qX zeRF(d1>q^@+{@qtamy<0lRJz~>by>Qp>enT_3s(WQ@3wV2AQ7V{lqBbi(=}zuqh6U zl?ssNj6dwV9k^qD)+IafYX`RoANsi@r+AUEm7}$P;y&B?pX^|fX6${ihUf9?7oVFW zjn^{V+kR*7sjmx5{wr?I*&Hi*cpJC}lC$H*;gfUL?tE1g!iMaNd*#Ml-}jb^zW@B+ ziYa-je~h2?L#eqry~}1lSIH>8t?2*4hymh}nM)7Yv@QCi#dZDX_ls|DMLvDbS37<8 z`hBNwJhzN5eYx*qx#XfXpLT*1#kP7i|}w{1T4`QwSLDO?*-A}KYF|lBF&V4`km+UurBW@=R!5ERXu07OkPv#=jQ%7;;CoOG;oVP zq2$`e5_|uHyFBlwz6e%1A1S)@cipwZYH;CecIR4Ltfdy)bi@=j*N4@8hyFT!s@qx#IUShw^s51Vt-SNyz&n@MH zU+xpMZ7Vyr#g>`jKpFB>>jR{zR?fPqfji_~^zNDef80@|m3*a?sp8t?8L{ehb9rKm zS8Lpr-}mE&Yh~POuiEX+k>8K=Gcc@~TdI5a{HGnW&wTo^+irG;QAiNxX;DWRY_(1y~<-2PA5`>%&D6q|}{Y3h$qmxprOf+}S$rpyzb z=`8v~l5^?rm!Pn05RHC*srG(Q%{iO--d{BHrq6t8_i4+;UGI&aU8=ob`L%N0=b~xf zZ?0MX?fmkWoA)s=G=z5hg_(j{o)PMA57i211qSXwS_RP&UncS{eAQ2RuLPb&!m~fm zaq0KFrB`G$FLqgp{ajE3XhTx%sgtMAEPDJWV_x7hQ}2!T>i+jb1@yU}T+A)r1*%@U z?20=tZGRzm*UW2komAz&vsIbPG~XLn&8fWgtSt9udhNW;uG_5{7!u?zo?Hc+k&dX-PV_Zl|5IIr_6ksbt=5LnE*6m%)v5ZL^ffs(Ct5yWR(%k~_4j z>wE06xLxNL-9A70lihE-bCH`~n6Wc3oYA(5yt-r_i^M(V)qh;3eq*pa)gQVuUX)>% z=AH8jTD(Y>p89yYkalXOar!EG&pU5pOTKao3xDTnl?cLJf zIYEkbC$wkj@2;?aS=?KyargV&=Ub+Hx4dF=`phOD28II@7EKQIedXDZSnD`-|LRL} zGXAIThg24dGWabkuwT5w0yz>+@XNOTj-Bki`rG54&=RMr4&hh5pPsy)@HzG5r_lGs zuj=`p{ix}`Q~PsO(EYaw*ZzKFW?;xp7xwx-d0OI&rqX%$FFgHi7ta&XeyR3a`M$|P z_nr%U&Ym=Xd*pO>1_p^B$OsS5V~45p*Ikm6IX_8${ifwy4C)$p&MRv1Bl+L!le62y zy71E2w|40toV3pq zVO`S-2jp-##V^|WJ8;GqSFIblvFb(7Z3}1To448$N~lN)93-xpsB-E;nT^^M%0D(Tg?&!{`3i=JKHGkGPb5@cXtNEU{8;d8Xx z!+oB5_n32bcdwfA+pYDt+#;nyq-xP%e}>3Acg1l34+v#DmM|~Z zSp4PU+rD1=9_?iUtw;e4`b)TgS6du-!xtJ^_HbY4BU=XJPOhU!70;tTdjfaJ?_E*x z-s^jh>uv}8wG#GkUwzvdGyk&b+(+P9UIvB(d5~oV4L8?pD!A|RvF@|xtplA*%MLwd zSQLcpKO1|0k$2bK?meG=e9t{*iODe&&d)5FGq2tp)WH?+%uBWb`4O}PWj18N$%6gC ztNw&dEpto~{rOZYXIahF(|=^jrxqn#-s(O5dUzsZ;6~)gSTNs|>wC6l{Yx$OYo5hC z_ors6{#pSWW;~GB37(>Fs4LqHc4h6;Am5NZnbUJc-rqFanG(5=ZI(v7o`RM*a(e!< z+2x^J#KaW__fw`l{`sw@XresCjSY7NAhCM6cOlp~+ow*xyBjWtUTe&~7`o$pB4gk| zWVbDOFWp+5z`88v=?T=j=+jisZpCHj|-kPUgTTDc>7pc~4{goNGMuB2T^1cOA^wZ^=8IQ9gL}aY46t=21)DCf>e*Xx|B3i&gSv~w(?PE5jAOUt`p(?N zbsQ-vCe?gzHjks{jJvZ{jr#6)~q*Wg&bj;(^VH=Z#?w(z@kgY4vvv`eOUL{_g8%2r}q)@ zzLonl*PowdIOWpk$MGTa+_%&?pMj3aH&jA1PWzKXwb!Sb9+x}%`{S&TUFQS$q({5_ z4sZRf+R=sV_NH2EuJ6%@#kZQB-lO`v>{HFPy)*7@)jRf~l}TbMcfZVDyI(fOpcMr* zoZwMMhK58}^<}fJOlDA@6PFsx{oS{2A=hK$wV-+A^JW?GJ0l8^_+rzro z8u!+3uc@)lzPV5H>hqjiA3rWJY&>Mk0&0VR;xJ<6iz6YEtArAaxj@O`_;Q^aK3ehz z%}zX>5wj>{Qskl)4alYGE%U$~_oHHuzxcVgrEu2Pockhg&2KLGy0Cy>7Tv@fW$7V5 z?`pmMblB?o^VJ4&J6)#CpZ+Op?%U@&?hn}{Ttb|Xi>9~5BJZ|G{jT@aTwGvjcee2T zmK^7ccC){+vK;?!0Pa13T&rCQ8XGKMI*(=BO*OH1vo-GZyX)Pa()wHHzjf8UUt;;Q znD~1_^$uEGyP+B=xD(z!>N#X9yQ}Lv*YwARYh&)I_+PfuyBBhKU&997*No4=gEb)2 z4ftlq?8skKVV~D6@_u&EwUzR z90c;gku?z^9*B@wTyS1wXU6==_p|qG?{S`g=&#Q4W3jUyzpc45+xWHN_fPqVI|$Hm;a~NGNU(<*psua_Fys>yLfMZ(G*( z@0`B!{KuN$MiE(Em7QTNxV~b9$ z(dkdq9!ostxQ6go*4)^q-&aq0+LC+j^lF3sWt#nd`e(|85N} zO?a%FlWMJwTJ&A%z2oW4FACq8Oy2k4*0rw@pKPuj+>-pSX3zZTs_Or1Czi_JEsMP- z91R|*0(mQ;ROFriIoZ$|@jLa@MPqh(8P=ZGjop4ea6+QaqPNEGHw&k`x;{06Eq0Au zRN(*Mm)5=RFlG5>x7SrCk}rP~k11A@Eq3qEy!0hmP&fVajpb|4XUw@!I(yx7opjl2 z!qI;4%EMIT-R|NQTz{vA&&;`&IDe8?NXN;UVM{ch{?0Qx{Wjrd(Mi=?`=-jcD`;uM zefF&vl=<%U7l|0Pi>`gVu-|{N(XTDPJNkBht~s_!CV0xsr7ZX3p1s(1ahFbV&cPR& zpfPfg*L3b%hTaQN)e=8(?p*Tf3;vNAn%qXU-dcg28PjgB@BY-y$YpbT@!nYNJN+&p z5%4VZM!2{kAd7{#@nt)aseDAHVyQ{?dc}-=Yc^fda%EkQ7&-d+w(v zPM*H;;?_N>QyPxx$II@h-1kYeWoq;_hmZuZ$mJS$HaDz$w0HI0^UG^>V&ql<=Q;5Edcmp34%+Qje}Y8Q#6=s5o@sWMPfd9?^LpNl?;Fdfex03@ zYnicR(V?WiPZy;+y3Q!Q7HF+z3)$CS`F>V>C67V(ryG^ilJ~rrvHQJYQSt4K^1;(j zo1OpoeoOMJQpsSRv<-`D-Djq5a-pjk1QwT&yz=oo$5^Wlwqy~D$KssX3w#7DlH_q1Yt zEZ6*}+b$L+-A_JT$@OOU!o5AU*UxWx{Nm5bYyRczSyd{blRmI;)g?p8H(Q z7w*@-5)$p7c9e^~ef;##`+bX&kN8R--ZFjT`CpxV2T$kR-B_%5z}@vt=#kL(;gB7Q z3=9kjhFsr2Ki$6igW^umhObmjjsH1OiAz_1F!G+cVoM(D%oQA_`cD^r4BT;FAaG%1 z%AU$EzeV3g8oHjXo0Z%U*o7Uv^Q&t2*q$~lDp|Ad#Kvb4uE(#ueS9xtmeK4TacZ~DuD)IF`mt^& zthR(Ts3OF_uljUN>+PmSA3wc2^;x27(|0fS(c&nbny_tQ#r~eud0*~m+*|Lq|$oV56k1Smz z9PInbGeK74&iU#8gP(GS&WL$6Z`!(`)7!+}FD{kT{k(>CrRVvHkNy_9svePHRbnvn ztc(9#cRk+j->mH)?!_Ii?eAaw_@J*HUygqMzwb+uAMJZxxY$Q4u3xq5`{{+yg>MJE zAg#^=H#F~@7jON|-`S=8Gd;C)g{J7%<=K~(t`;%Qcg&1+IekZD(Wm)m&!_3Km99M> z`TX#kb?bxH6(3b(F!Q}W{Y=pm^LA0$%U?CmZ_mH{z3lZWo4k3`eB=1%9;<2nJ-O&f zhTXh0ho3*+gBQ<&(jbqzjbOy`c`V1?+zZWHaY;|+#FIbHqOHG$7b%@y!c-pqF*WGq z($yOKP9B}HPG7^te*RX2`}=Aq`~FutoOe3)a7^6ER7F)W2D8w*{_Z~GDXHzIuMI1% zmDcQhxog^P&$#^B+9`9dwsbZdgu-5tQvEP%Atb6>*Zt2fGZz^}a_~tw7>sGUC8$ta;Xm9UYNC^{Y zOG(ew^&y$lSyPHc-uYjPbDw<8|6iOTH#8{wbC0I_Tt^=_!$;} zDu<12o8D|#R61vJ|I1mI{w#TXQg3pf=x3YH@2|hKu!)*y*S|m8*5}lV>!%-f_s5FV zwr%x0c{)HJv}F?1qt;tAxgL~+diE{y+AX6n`SbVCYbzr*alI8fwdBw@oy&ef_v)nQ z^?$3<-u&WIdF;kw>1(eyJ^ADxY;p^Pw$M8M9QkCEkN+&@cpmS%Z1i?Ueo*B8 zGrMH0&5rM@J+4?g-Tu^`X=LAyN)fn|Gcty*Ab%)d)7SNrEPqq>cuSk&E2P3PdPB;Y+mPm4)|A`@j z{bJ{SP|TX`k69YaG2z{xSkczs5g{Kkv!@4s5>->5`IT$R>^1o}(*gDw%^H`2OQ4GE1%J33Y3V4Yo1xhZzqBsAG=G-aUPuAa0i+wt`Oib6- z_2?pBy&&G)pojOanlnCl0`lO~lgA4#8m9cKyw6lIKl#GNsVCWmdh?5Z^?2{fvzD4& zGWB=;*@=t)JqJ&EFfc@jKlI#h^;N|{?fjZcdW;@2t-tGTyI&B`|3JqK;qDOU(@zh6tIB3#*r2y)Q{_z8(=T2pb)@!OySnc7 ziPd+^3%7z7q%bf<@IT~)_+)?7(pZKG)oSAJ?00hgbxD1zws}g(A|=ba(*pP8pT9g^ zPHlF`j`FR(3<}cpOvmab#L4wx zn*RSz+b%A!pPv!??2D*qt;oLRueChRYHLXe?%v~79cvo6gT3M3r|CC}K2>Y47M1-y zwX^T|;>YJJyI&?xp1yr=&gZQ2tN(mDbSn2t=)2c4`+m$wh87?ayI-WlPp%S@m^^<) z(7Zp>`Jd*m(zr8UqvJ`C$3Nrc{+-KKZ@9cXMo6^0Z0nRwX}4bIUHp2z&aPwX>lHnl z_!+)j|0gv2(cf1t#~=89dau^Yxag(%!3nNEGitVEZ(CSW|8U~spQo)M`RKrmR`uVn zu1r2KW6u(=-%s|>(YRy3?bNnYODA|9ogL#ab6ISVrtsA7>b~|XpKQ+jRCVIy>;0ae zH`~2d)cW$|Otete`TW`Qx+9+i`R|-pB=U}N!Tvw{Zr16a4crsI&$a*e>BmPa`z?=a z=vVL56N}vTLZbG%%lz=%q*q5lBc}`u49B#rE<*ADJ z*m>(7f3-V4XK78(j?Cko7eD^ov*_{X*h0{Zfh)+~Pn)iF^Fn;y6Iilny{9c_&&k@= zTm1j!7jN5JFyHlcYS6N&g<_ib&If&o46l@Y9_fDB%QtYNdiuOawUHLLwit;r7|5L! zzj5nid`5Cb;7XhHd5@3!UX3&VEVb#t{t1WO-|e&cwI@q>F|_t$P!^y2B0I>RDY@8n zY8hjK^knYu^FvZ+pN*a{KdkS)RzrDebeG-cb<@k1tzEuO>FSo}tJl`d@rqzLVDz-9 zw(`y;mydN77dQSFI9~NrqU74+vm2$d{_>d4Z_N#Txi9Kk)ka8B(O}r+_r-LjdgI2M z(NASVZ@D()o{A5@`R!2cwK)B^t7nx@-BJ8YBwOvSo%(eDTinG73bVvkm{-b&osFWhc%F8h4(-Tt?WlRrl8 z4-k<3{{B|g&JQOpzFvHOwVBaXwBFB_v*)5J*cG07cj}X-=3dvEQZm(Z(It;_ zI;wMR`sdWl_uY2V=C#57WwA+Flj_YqufI(*eE-(WQ}5T**N&a+4AYeBH?FNYulv|L z|8SzC_S_2>C4rxchq0M)(&gc8%RCu+y^SS!otHdr!$(_a($N% z5=&h+^}+5_%H1NR+dM;>-be45x!vLZzPoE&=NJ9H;Rc!kJ8I7m(YNl=UZJ-Iix&Oa z^J&kbpHpvcUG%eQ(bH4MuU($DJp0U)=i9RvonK{U`85eT2AC`|_X#*gztx7Gayp>) z{BEeoJNqR%soyV#DxLhiFgoPc%&iR~qVIoCDW7%wY;c8^{P%4yweCD;U^xBQJO8w( z?D_M>b8cKS^*a$6e(__u-@nRAnR(M|FMWFcX4Q+$t952XCS6Wlf81?5~)_Zei^SWKvew#e~Y`glC-S(494%PmX zW!Mn&^VGMu-xJy{+nJr6`MqMJ?yT=t{lbf$&NQvPmOD@F{`G+KhizTvhvpW&I=XB* zc)&2K-?Clb?isf~#C{%aD?`v6)wP4)!!=Jc&Yn^pdh^<$+TaHr*Ssf3b{kaAyI3oPMm{AbE+u3NQR zyiGRO9b714JO6at{o7w^wC*r7+)lb=vC!t<>H2)5?r%2jT3_BzdvSXGB)pLf^$`)#L( zatt@jCim&?_Imd7d2OHl;@kO6>%LxE^8Q!nwc43>pI_t#t*m++q?^6>#uwNYj{{z^ zc17Qx#;#OvEdAUZx@hwM*HiYd+O)Ry_nOrL_L`rkSc|+@&lU$Qp#nR`>-q+6=Z-%|JaJ)8Z$@LlPMZsgIm#UJZ#bAJ!K zxLfC#i|P!u&r@s`J@WV`d04EkZI1Z&DW@&pmaZ<~XL$U0Z`Dq{>eU%q!~5ike*&hi5HNVQXY;AWt8zAPV!0n$H$UXpjI9;- z=S@HKx83D+<+ZQ3HRfNJd0lC-(m6qjiVCf?Lh_TQyh~pWD;Evslxp3}FMen@J$lw5@5poW zmc>5FyQFvC=kk2{_UrQ$t*pq zG~N3Bxu1T$kjnq+xi5F#ai?voO4h5`86!OPw_xfD7K3F^4*gYLowIn;mmsm&UsE=< z>m1XG;kX%a@z>naSl3iphKAfpr{}+Yyl_s;vGl@=I}#?@)ZTacY4%Dg-SlMgRvX{R zf8MYAIlj_Yg z=a%%!iW#kp*sEz-^Ca(gqb1xTo#XYZg+v+3jBm&6c(0XWGSl_?9+CISm9Uu;u)WuJ1bw>Jz8tnytKHxO!csX65_|-FmT4f6n>ydtFlC zd?vq{p5G6@S#+qjPl%IY&HO+AWcRLrUXdRkv(rw!msw4Aect0Ait>luUoY~!Zn4n& z_c!kKmsf=B^;{`FSrV4j8Fa2obbYUnOufzhy;t${r`Xhqk={zPQ=%G|&0Y7^Zf%9V zZsoq)e{9aIUR^MsnPH9k`}e0W+ok2oH~U#Hn_ISL*7NH%7ft-FbAN4|z0K~Kc+=)d$O;3J~ zH9!0D&z{%Gs%9UvzJFb0s1DCMSASj9O7%>V4BYX4y=Ki8V}Hh!iMQ9Sjw#c>9_=^Z zDezzI@3OPGk!xR6o&Ys-&rVrivQ}o7%*Bm`=k^@m5qtcM@3o)Z%d-vBPKCGib6tBa z5xz5i4m_}4>29w0|2&j0{HEE?EfY`cq;}fD6xOPG55N}(w@i9@_z2jIM&3ycDbkP>r$`1 z7CNBy?hGK1P_DDMV*lFK-*&B2Y<$CSD*fN{+N>md{rAdi;alEa|JgP7wVJr|t=I7n z~8*@#lG&XX3ru zzr-GY{#f#RTk4|w((7wZY|+;~vv(c%+@cj3d-+cG?~UU@>=)dyyG`TX|47l+-~V1} zwQJw|F1~hkrR&p$`;Mx#P3*StQexP!_tx>GK5nte{i&y!D&~JYvgA-j<+k>a=Tkcb?NV~ zlf4zX%o!Y_?c6ftyPw9yw?6GUrU~k<$O^k(&R?Bj6S%HF!}ffx?z)QlCo8VM7lId3 zJ*T}_KM{ViGvvM=+mkziPxpsbCQQw}8mE4l>(<*zuJg5}-H!Iiq<#>d7P~7wqH=!F zy7*QBh6Pvb!ZIFlSD(IYdhf(b<2cR!h2oZC_gOAZUv~SMq;Q#3@yC0j@1`S075CV! zI`w%~jpx+kVU{7Ox6ej>%bTv9rg_%gRbuIH;a?)rzod%d=ICf$-1$kB;li?t{Z-kj`+7^K`rl?T?=sh1wQ zuzr*|e&FsW3+46m*KLfSf2sD`ytIcO@6PD@?sflk`Jd0H6Yf5b_z%hh3=BMakTx91 zrroPGE|%}o@wa~Y{c_%Z&s(q8dMRrBCceI-s7$%p9zW@8aV*W~ig1nufSrvLWku4m@c zV|K-5{``01;%m{p;oA25!P_K2{%CmEqR#dD%47zgOL{A(xOSFxg6xTX(s4@i_M#a6 zw_h~Zn#@e!Yku?DS9_7xYKDZsiyvDq{WX~K?sxYw>4_P;G9OQ0+G{;asO-zm>k5I} z*g=LeG`tm8;Bap^XucD|Jc&?4eT$54SNqw?kaud+3@zNi2Jgs&bL~-jB2}8D|da%bq_xiv8gC_ zYU|v6uQzP*_*i%Q+V4$Iocy-b#ZDDtNQhaK{5v{Qo51*g3k}vm*aAheg7{vc3;}+Gx?h1zv2^0e)Gcp;=b3; z^JicBk=N-)6^`^vKH^m&+c0ULyPb`Q!KdF0X6$e7#_9X`CMM{nS{6(pAww(L)`S6aNyL1>fM1LxDuavp;F?`<8vPizwMKTZpI?Dh#(i@N67 zD*ANN$1L%8#Z&GuG=!cyF8B8F#5px{4sBSJ{HN$oNT0Zx?BVI#&qtQWzE+j`J|#`o z_}P@#&=oHUrO;s6yw#h>S!~beJ!(_W7hhXyaxs)+=9eG4yS~J3pIKT}79O(Wd|&}H zLzpjUeB97+Wv$J9(`{MjmuK%PojiT<`Au7{UtVKq2i;a2rVn*^cu3idut^zRBB{Sl zTz<+_H?wqte0j*alP`n6o!H)=cYEn;Z?4+w#;ZXKs{-?w8N%K!(z;ma>Gx0eaK`%E z89u>s=gWJ=&9;`@cfbB)g=d_3rR=kpMLqqHkZ&l2YQ2b$#}ZzUvbY*)kZoP3{Yfhza@k&dN@||M}v_|Gs_v zXD0i6`susp)+DPyO}+|?(`$NqI+J2rZiqOGzT3QFf$_?KNt*ZiCxZ-1i#@J&Jv`Cy zqG9dyZxM@XdE)=+rAL?a7M}8vdcS4)T(f1+;E?bO0Uyhf(--4)LbIDEurg(0j+5q| z*h!kk=RdiAejO|jxJO^ zz7`s70xid2n>`!8URy46>3LW4<`m(QwUaBo3^$9Fc)h)Cn)r1WR}R;Ah66WTj_c^C zY1Q~!FVjCR^7H*giN)`CB;56l{#JCxB=bz@J-hViYtZFy8*W1rM6K)5wmC1fUI%tA z@eCF?xn0|v_vib|r-Wax$^QFv{&}TY?)OWteBE>AmqbL&7aNT`%nZpTfuhHC4r^b} z^fOtPm3@EN88g|--y^>j{kduyr?q^h33z`C1H*w0P%CU*!^889R^FND&7HdJ{ely(soohEb;Mv_)Spf8W-dA@})A`_V1yOV;mUW9W(1yZ_vvC!@zS_s!)}o%x$} z7nNRk-u>X>uH63f%huLI<1P`ph^y+AR=2sNSIB}JCU2i+8?F5!w|4fN$}Yv&&p~_Q z*T!r)|Frt4DMQ0sVcE&wE#>AfU$ftH(bM-q>mFy^o%Z4VtVgQou$9m#DqVMSZ+Jt z?lsio2@=qvuI|>;H>p#t776`WCU#&q*L%yiU!|vd6}4%!PGOy*`24i(&Plcm8&-b$ ze`|8T;^+Sl4%a-hR6C#CmTQ~We=O*&VMuiKtqi%@R^OH^2aR_AIi&2qzQ5?lqvT^H zkTBqZo;!5SS+Bt4XzQj48`dZ}h3MS>BIFtJCF9bX-zyp>8$a2l`9Asb^`EPrCb zoDOtjW-#-#+m_i^JWFHQx#fH1ia%|vG}e3Iyr}TXmcmy{HiiptyYsQB_CsY_AFLnI z-0kNBTF!oKy-uRJYJaMTV3FaXJ?qQ1Eld!qy}7{i`mRVfL%Z|ElArIU?VNJ4?p+b1 zJVQe8lz6?8J)38EF8yGWcB5_MMV&=oP0lXaczw$yyJ%SFp#{hOYQ8CdaHg)V>Q$3q)Q+BC%sDg^y7rZ zg6QivN^eWt${9@(BY&fNRE>Cg_> z^O1dq<(}c&^VO&S?mccc1(ui?x`X!ImyKOI zQRa1B%%Y~5L2Nn8Q>VQ>t+9bASyWcsFmJZmwv2UE_HSQ(sobe9`cD1tx&QyRHNssj z(e&u=yBQ&05@L!N&FuAe=B%E;czfkd{xwFYz8{Eq`giijxn;WV)XZ&i+e-K5vclTp4R6iXh6JSDs-GwydspB6V#3c|^PFGHC6vES zySY#HXf?yHu4Cf0`+e^AaR2tR7pvTJJMNiL?KE{M;q|wx79d75zP*axraw8~c%_NJ zRGwM4H^#)CUtYT3Ds^YrPSI`A_t>ZWeq?y`AE(6Xn5eVAHGZ!w?6~K=O!UO<)1NqXPRf+Kc_ImyMQ_KSMXd^`L8F= zU8Lqaee!9R*j=Z%ll5cQ?Dkwb(eu1`_sd<2wmxFbnO>@Kd;8)F{vOv7o2lPdzghP! z^mg$r*Do2f)RN`?`&Qri+&6#G&rOW*XxQc*K5<{{9F5Ycg3ClTcI7Sqvo|L8X!Yw_ zOVGBx17$O|$VW`qxX-*>S3KprHQv+A*5blg67jOr=*JN>`=)x9iqzObEZ0dB*(9j+M6m&faV>G!TVH`Nu4A2C=Jp z3}>o7?O&Z^=v%yhc7U12^@lpGJ!yA!qaMw0{pWNKQ-TedTrj}9h!Fzf14G;ad*p&`HZ_4zP8Z*e_rX$x^+R7e*f;8^%twH zojvEzZyPl;v6y6psaN!}rmMexeo>F{jP?8%g}JAv{@yijvggsx%Dlrne6{XqtvK}T z)cU_MKYHSR&zAXZnqaPRumAj#pr3n^Py1dly*FFz@j+AAusef6T&((s=bENlyoI_& zo}T{sBq#V}(lnvkFZ-FE

YB3YSbhAQGHdT)Ow&cFk{v2UeBVUi92py7iOAa@%P+ z>Cwl{Ctvga7h9ZK{rC_sY}pEfL0+ut`$+H4Z*RM+e%yEKX;ZE?cga+3uh`2PVQcok z@cMpg#fF*NzP|c4IjNp~jaSK;sgJY1uf7~A*1ze*kD|Lizm=S>&(M#Wzq#-AO;?21 zua`R2PfI$iv^_W5eqOH5B=_^4o8{Un_DO7iw(9!MFH2(>XS?65D-wC%7~0wO{hQAv?o^<{A;`?obdo#bewq(9m*tNIt(JWXJOIX@Bf59scTTX%KEhlW>zg2pBeAC+S z=d(jxGTkRKCD@w8ubTKWu!c2k;T`?ocM^Vn+OhP`_30-c&v!G4{rTJ1B=e8?=}p`7 zUuS*Ke)Qw{bjOEwutAW9gOHUw40m_=efq9hvORzEiO#=@&$qr)6V;!kafi8Q{_5N3 z<)2n3h>Ao_*WbleJvrlyxbObXP*vAWf1$Cp(DLsBrbNkyLNE3$P9rQ%OWv{i& ze5^m(pU;(=9k*mP{=W^-L_@vFq=rFOPG$1OSCeIIXqj$U#A+GNgI1$C*;{0IH(dakEj;{3hB;KH4>puz>O|B2~Jui5N* z-qHH@o_W`N!ynqY&*V01-t_lX(tb{z;OhC4zrTMouc*c((`Q%TtW@cwor?;eEUR&Z zHWXo-sY<&2F1XJOVLPGzWijaFj(nq+|JpQU)~sIl_0pQ{FUq^VKVDwIKRfqQcFZ*C z-vJ33KkIci?w6eZM<(ah4)y-eViT`>U$+o~1i}O8v7!Z=Jk?+9Hd^_{;PswcfjjIi zpZ@AwcJo>3Ha?MeiN#NomaV@1zV1_TLOmDH%Iy1j@rQ4e)EZi+2k&13F^54%3(_O= zyJlxNf3o+Tvsb_U5;=HVZSIfxQ*!lK&up3}`u@86^{GL{Q?@1S)x6!W|84bw05#de zPwyX;DCt!!7J=?mYJeW5S>RuL`gW}PXZ5)!&hPKe6HdL&{rK~m`}@wz?q2;@#`I)r zY=Z66r1CGjrhRnzDJS7{{olPpncca!BBCG4LEJP$2jV0fX@#dFt4@y#013&re+)$lP6IbNoh0@9E-C z@8NEH3F%loa9$+*Il}w#m$=+jQdfs;8az?`slUqaMCJ{{D_v zwi{Ng(~r5ie}h-$8}qf0!zmdUF8D+GYjXr*pYH1~JGr}4_|M@P&sVj4p7J$v?xq#1 z3;18F|N7LkCVfhBW2JAEenV)-vG=LZB;BDMzS~f*`DMf_=1)7c?UC&nz3E#UzD|ta z92;&n?W?cez1#B4pI>O*+g$mn!hHIjEzE(-+V>hGe(p@{U4w>TQYnv`?HI6^&9kW+B~%==V|>?RGq#2y@Z{=`N|#h z7uP`3{B7vYGQWr|CG$%~-#?!zzGipKu2bPh!}c|Oy;>@oeDltYbmq;c-iub=e^`I^ zhE8RUwCLsXXu0U^^1=7@ca`e~hkx1vIi-|=VS_F-j9V1FzDHj7tWke`Q|;~BcuwwX z-gRf6>7U&ae|nQ_;2!oHd9m?o=U+w_#4js4bGQ7?JyXB;!pW78xWB;$=?N7$l?c_v zb$!p-ykf=ddbTy~F+1}1x20MyuHoNM`)kVO^56AErysxiWAVFh{c_#le}A`R_B@4T zxP;5F;JH{Iy#GY!*Kd1f9Z-9{%V^spHBh1G9scHUW95@t_qzCv{E_^XZ>0CKm!!se zy_kIJ%g^$OFE%%>%Y>NJ;0ryvNxft$&$ElAN%yaQypb~b)UE>+nzy~@Yn)};eByUc zk^H3l=@RQ#q}b`c4sd$EIVN_|dG?;?5F-*MLmkuZlKM09G>h{Y=4)q{9Lm%C!u0rx zo@WI;x;r3L)2k6(S8dQsy%)8vx9$8VJPxL?1q-RJkIceTqTyI*L1e|lq& z%x?dvwol(6r@%8DSOz(|HX-<_@bkAqHUGYES$)}6{rT+MWpjJZ+rQt%o3qI?{AAdU z_K$(vrYkPC;VwD(-uz;n@^QmQ@3Q+R-+n1|ysWqSxJJ~mpz6q7$9}#2S>g*Bv}IVc z8{$SYUpvlw%Uuj!ru;K&a=}R!3Tf% z{F>Br0(8=@QtW%{pQq#B^x2qo&zo#?`B?tNALTvC_rE3g?5l~dd$=!K^496>n%z$A z{da1Awz~Jl$v^#Y^mvaYB!ncwAlY;F693KxXO;**3!J$if7?r!n)zR+ED!X%;dAlx z+CL1BC&f=*m#4qFG_ybZcJj4P<(6yUr#@_X z>MkSQ3nz|uUaV6(Zuq3g_33Ag&pj7!eU`XfVG;E%Gx=Z9Ew|sr4Y^XE`{Danv`hc6 zgu1`-OxQjp^_!w!+;sKB#fv7tntbZf@6NbmpZoLF)06f6)AFyC z9%<^UuagvcR}5>yH{=S=WjU|DH9d6R7rp1EFH$CSnr*#-cne-cG+HbRq|C4urS&!ix$k8gGf@9m= zB|-;uX8n9J`TkRz-*bH4SMm5Q@xLCIUK#6mNiTWFslUH6#h3i?*EG-z>buVGmWQscKa8GMn9nMk_OamiHp}Em$LGF1Zl3$*)>lhMzTL192ZkGIll_wXtr_F8 z&M(*D`7+o4dh*Pr85?qM9c|5gy(;#}jHi}qGP_RgIZ&Z-_w+88gJKDmyN+#3U9>Uc z{>xjt-sSr&I({R4jeUOP{pTFs@G~NFR+lmEKDkDH`EI`t=Acyra}4^o&OhuKzBb?A zdhLn>KYX-4F+H9Xzy4?a!j8E0O1tOo$<;c>sN)_Yq(Aw1YHaexbss-0*%*C2SFM+~ z+D8@$zx<{@ z&0PEX}897+zUYSm-xjsph)PqT7C2Kl)33#C7BU z-BdZdYwnWlztXq)zz0!)g8znDtoq00bC1khmc8%uoF{TcakJO#opaUvJR6)&(1B*ubkc_ln%6h`Xis&f>gnHD{8iUw6Fz$u0cL>09TW?|;1?cR#gr z{?*_e?PpD1Zq+qP*%Qw2`9;w<2O`q^l`|LJz z>w)tkuwKT2WwT3+swCAH?0%;|-(vppfX|Yv*F9gbS8nmkMWuK3^uEN#$FkOy`G-IB z>=ZI<{SyJ@JNNqEbHOw8pPQb4dt6UHxO(TVYw;?x)_LB4Zn0~Bscvr0q?lPb z_WE4kH$;lOPd)MEJ8!Fb0!Qq7|9w|BTYsupyE?-zSBpKec!b9{-v7h zZ*SJVUEQN~=kT+jebZeZ+cHT+ePIpwJ-hHk@{Y7WJ9Ky1#b*eu?ax@Vd*yuXH7#(@ ztlYKk-8AF%-#lMus;}AY*OMRZm@uhm@0n`XeU;aXL!4d|TZd_j&lZtLu-3SLdQD+z zb;3-M_cwDZb8ea5J3G@Xtnb*K+Wiasy5T0C+~w!zZ*3_1Qme(^I?*;?u_oH(^)jFP zUq$YksjvV4&E`|F*^_OI2MR>qFZG}DwlVa?@0l6$Qietr_g~#&_;%0Z zYu`dYU$3=&%LR98^c3|Or+ut$+Jro`du>&@?`-OO&(Kl{%f9)0>P~N{zP0p@k@Ixz#;aX%_fJ>EKCj&N-rY>wIsUHQ z`PDkN%>ySeN9bQZHet5GHSOt!|7!y;ii`AgK1Y=Ps~oHdL-=it?K2E-50*+{vDp!sM%ZAYMfwP)4#&zp348c z6NbO@q>~~)*(@r(*0X;m_$VMyR@u<2Ci;G{&f_Pw#hp!cV`hIEZ!tK3#Y~8}Q>n~Pd*I9MvI?rDD$1OJdUQ4N)WgeU6 zyRFt|#&r8Af6HU1i(xyv5|)COtE{^+`M@e4>&*F+mq&hSDcKzoSG37`o5x4H_3!sx z-W2ur`Q}bgISD#^GH1ug{Ev0hG9%}%Gg@=VHfLIlSy$ZaSjk;8 zR#fb-liany?6uhf1$Lc9uWQfmOO?2M?DbjEx3fz`zidynnRor*lG3!AJ@5myVQUt` z*1lfyMM?eH=aX*~pKo7R^iom3+%LFehU@j>%S&zln#El$ShGl*Py5c{XD_wn{k-mp zYuwL|Z>u}^dE52xufC;Oxjo-kW=V$;Om_ zvG)z*N`CuJxqf_0shMZx?LXgR?wvQB2AS(_xGM%eTH-+73$60!XNp|+c!p2EJ#F%} z$$mekzuaxN%+}oUVy&OH_=c*TH)eQya`kQ4`RVoUz4r4=Pix$F|E|3Hyu|$I_nrp? zcJ2FP{G~2`^3%fmOEx}P^LTeg&c4j#PoDGbpL~hE_hMV>titch*4l#-Bm;xQc1UBr z?DUeN^7)fb$Fer&YS#Nl)yKZ9zEn4_l6gaw^OSeRMU{a+>xzxOT%Gs1Y}<*epd)OV zodfsGzrSQtt>68^WKAjObH?*GN`2zysy%l5_NSc?PcihkLz?5eGcTQd1{$XDtH{6H z`O@>X!mrB7`;NBTxi+ZPK0jIXglTb+V9xS|Uf+GE1@4hwwMO^yD|RDtWo@q|vGG7yq6ph@Af1M^H>6==#1> zg&#}a8a}mo`Y}jO{8{C*CzZNZ`!~)oSvRj#=gykN8FjvpK!cq!aX`Xn*QMxUrRqzZ zul{__y;S~FU6dv5V%+eNyxhI<@AI+8aKEL>H|IV@053qsTuG0 z%Lspe+HP3Bo6qgCd;Oy8IV;2?WzLZ3Nq&^_rQ~ht6z{K-eWI5CEMGI}KH0~b;TPp3UPi)WJ&p$2m*r$s#Vn0I1Y{DQnurzLb z8NKKJwaFLWel_;2ocep6_C4#F`{UF^l|>~Jf3Mk8GT*r5rge1c!|J=nU#`CWyeF)% z^taKy)eJY-F7Nvj*LQevQQbUf@`->RU43jt?V+7MzgfPjoj!fj?q=M)3G>U8_Swdk z7}+n6nZI++dzSwezFN!l7ISd-++PI_i02yj{LfuT{hhP@+RjV12hvU_pSD@1v)Ol+ zVa*r08JA73^&NY4d)qrmI3$!q(vE>!?9&gv+Re|Op6Q&T{_6JWyKClqsy{q^d-|#Q z(=+AVKj*9eIPUsTuH#ME%YB`n{(dt4Vs-CruG>qwh^>3>7w*{;Q>ioac>l$^{ho5| zdA}q!OD->8n*HW}M*r2@^P$@l7*2~oGJ3>T|A_oYoX^uFZ!b!!*}cGE<(7bTyEFE> ze$HQ(`|sS2Dd&SFS(R?&?T%e=F7jBN$IrSSt8YKAd|K)Ded{TWyUeSv)Y%ulvDuU9 z`dQ=hspqdVE>(3LdlEbAcFrR!$XHWDA>@pngfp{*uWz2SBu@R}r&7^JizmN}Re$t* z%Jbdt)YF5nf8X^=>#7znW3t9wbIGdtTh<=^4N5>Wt}VS}yTR+5^J&veA4}cYX3u)2 z8Kz%b{agDs#3!(`brRl~{ajM~GycQ#OOD~0iPuUm+{p`?sCa(!o_-_0Fa7?%ErWKj zZ!(Y~2^eYF=K{StY%+}AjB&n~}Hg|(88vmh}Np$-`Y z+OVU>$aa3l`K67Kerw9-&dE1={by6z#QomaYoE_Iw%ge;b90Zj0`u$ao~5tmEIj@A zrrPxP?f14rgX#81Ikq)H*Q?#{xf|{Ge0*cw$Lw&%df~2@7dPb|DuRp{94Lb>=lJKXGiRj-_>IG zt#nBV|C*@l(yVH>1XSfX=15dkLTDni;a@Y5_O*J#^mj3k!Y0`KedT+VWfgMjS`3G~Hst@;J zSr$3{Qtde-uVF4p#1R@&P?`Dc4RTlUm9-#yzIcPC!@z4Y&;oLjH+=NYD&q{XIR zOlSGr2OYqI6?Y7OUDYq&KFQiN$L8%8;kT2$zFU5tv!b7iCpOV$(W4g^SVI#&`)CRF znL59}eQjlheaXgR+5II8?r^=Cwyrkq-y@;FE{`8p9$OPxtuyEN#@B0Nrpd$?Kn9H( zN})`iX53Unid5MdV-f;5w=pP%}iz5B;j0w~xPp?E=HD#;?`$*2iAE>;3BH=cXIW*6yDIjpi6#?c3A$s4@PM ziR{w9m721CmP?*F`B4Kc7qeEp#;O z!R1Y_HER2GPNjceQ@;JnnFIQJ^1tl9|Nrm)KX>K-eOrI|&ySngpp&c_7#J+v!nWyG z`ZDd=9+Grlds^W1Z`0$_mz)WhCjQ@C+x5!&itw%QwI-Mha3cvpXSUtg1qLH7>1 zooc-&-*s=V%6e&c<+tWPb7@WJVIKu6VP;Dj?9X~oll^g@+@1HjFN0tnw?sw zT7FLQ=x=SW*B_&C ze#_{9X!CRLqraCPsyEx~_|J0d`{2$)wg#^{m;Rn)eCqqn*D@2W*S=o9Sb@DD|7~gQ zYyE2Ztuk^xu+G+lx?4^MLQ}tK$Zc6Ko7w$6@?l(NqMtWwWNURoxBB#V%7wW$y8=p6 z7XE#)KTrO2)uZ_B>x;kq<}6t9%lOdu&oSS`U%9{U-~9Xf(s;e+-7xPmoKa`{d!6m4 zeI!SO*1hBVk5{EEZ9fsZU*f=yZ2^-1?GK0rnyp%|?tk{;(c^oM{$70g+ryWszlB&W z6T)t$H)n^fyX!wOF7lz>qkmK5VbQ~IW+~6F^?y@;todOV#MP0hyi_Ir((az6kNyU^ zUF&`AYN_C58@S<%xJje!qpC@@d^V=WQ{JaXv|GD7^3Awwy!P)3$z@DRn)oZfi-s)c?5q>c{_n47$0GB?#Hjui5o8*Xyy) znY#5l|NE}*@1L(M)zG}Bw`AA z`mgox*7E+ietGW9@}`Tm5X8N7GYJ{R)z;Wg}E#3`S%&7PkY}Vx9$7s z=ZXgQM}HeucYS}C7zw%V9CQcQ^j$vj|I*H&$qie^3Aw=D}8l##JYd$kAGF^`t@NNY~bKP%qykZ^AGBM_CHd&}#4Q{O*+p1nUo&i?c?!SBaE zzORvl^~M{1USa;V_tm4C*+FU%+9D#6D-{-n6!_n)mYlhfgX_-xOSSVP*X_JF+lH&? zK&+1F``F{ZrZCl>uPyvzbNhPaqq^PC?PjwF?1Ge{3^V*~JN|bv{#lbEkjnH<_1~w| z-*(d&<~VMwww$kZ?|Aj5ZMryK3WW^q9&;<|`J?E$6j97xmEYU9WOseB8h9 zm%e;7mh*z;#RNOU8UJ54{;oJ%C98BM|LuAyZAOOl?$5 zyY5fdefs;x^MAYke*4kGGEZq;`bW#6FZHfZU-oI7-Ch3=zIu$I_|(7PuZA<~TfddA zugU$k^7QnN^}K(W!@IKme*aWVmelnA_4?cI$HhVwUEhokeqHNdc7Ly{$k*4YKeE5` zBU1Zv<$sUvt$1U)z9P0S{eb)rmZiV{cE-+gU)mN^+n_m6Tsdl%H z9Kr{2Zx_@)>Dg}~fBMY7L-CvpUjlaBmtXojd$9t$&B060-=9c7wLR-E zh8fd1eP4;ec*mj=`J-@@?ycFnOW&$yU%xDA6x6k6(Ug7a8`r1TpBH)3 zpdPg=PwU=)t=-q}eG-3l`%~Q!+i!;>S2mArgC{W0%X(?t6rcM2mU8RQH9O>QF8))rCw>B?fN02{I`yr0 z++>E+)4IM#{}=vo#{Z$*j>(I?zNb%no9+I8(gDGp)i3{beb=@7-*>BQY4wAZo6cK& z=ij-;{rIS*cj6a@7XbEZEVcvWzYS;geoBOtH z-%}g%E@GE_-H@{AMm#%&Lx?OC_ z{^*x=VSAreFRgwsdnenoZU4UhEdTys@6q4e0_6YKo6W6;da_~@B=}x@&%1E1zU-gm zl*ESJ72&()@63tcbGvPG@$yT5W$Ps0#QwaOuJfVyRd({9{OgC}kM1qY|GDq``g8Ah z*2m8hhXh!~Du`JU-`DXx(0{;K(7N={FWFN!xB82{e;>4K|Ly8sA^XBjSoZ~f`{}6n zZt3C>`R{k0?z8@v|L69J+HaTQSL?rw4Ey^lb>F>1&=H~nb4X}yID096pLSYye27M++AYkzx+(|56ryf=-`pYwQWxa{n-!m@8~F3&5yx9gAK(;tDm*!Nw$ zwDm{V>-Xj>Pk*$1xz~UF`=79=It7WUzeyYZ-HpE-yzl>>pdZ2EqZ^}RI1`_$`qJF?#!UVC@^bbYJd>}PYgoq#&jCKS>Hmr-YXa{uqs z-yKZ(2efvdpgyYll0t;^GoobTG^ z5dS~NbMKoS{g6z_Fi#88Z#Z```oMe17wPl2HgzW{9*~y}-KH<8xrKe{?|5S^yX%{t z=5(I-Xh_zLUF>eJ|MTqb-*4ZD-}U}z_d9a6?#u6IpxwedP{+;DXM3(3$hG2s(7Z!` ze_rF7b4r(CzvZr+`1CD1YD+iPKG~e`td!eb?A_}lMWP=V`xaKz_x#$o>ulinn{T0A z;|BH9;6P)T=k-u7;qJ`T-&P;3_MX$~(@%Z!`9W80X!@2H^K>7d(74Ymll<~?%9GEZ zqWAuO{q5st1Q@KJ~4)+q&J^`wdFn+^)SnRVyZ` zxg-APX|7Y>8Gh^3Uemn0e)psGx7&ZLzq0i-ER^&hp;WPG>8FK zns@&jDsTF^zGsuh0kgutGCx&U=Zh@;{${iKzg@Lcp7d`Dc?Y$9pCcs7KZKM9Iz5#8 z;pYASYQKNVl7Ja!>%-;+?ArfJT=d$a8vX;4ixT4N?;ZJHzvY{$lKZq@(BwBWHTCzT z>naTX&-C4y?q@99RNHy!Z$;JFkX`zL``90Ne2rgdznEjb=)3b8-=Gn>8Iocd7W0F?w>{@&u3wlx_iOm`&4+4_#P9sS;r`!$ z@Bcr2|L?>0`nk`JpO2dial-*aNMYKrTiZ3bAz$SBfz_uUL|=OSis`4Y*Z0J4$p@aB zILzM>x9xhsxxoKl|Cn)o7yfbC{?Fa}Dd&xqVU?YX9;7xcNY6ca;ehoA-FHFT`g=sy zO?~%&hWgZZ-c#N)UQ1p2d*XSmUu#6)J=VW(%m`~@=uPGInYu@famVU42`eTr+1#Cb z>YJ+arcDoLO8&gFqiTQun%h_Rui0S!(N6m`;AR2Dhs`SvswB<`pvIKkE(Y1|0(^gaW(z7*`fk_ zc&nS?M*7+>93M=|b#G7aIW?^?EluyPyIbn%9nMqUGyc=C`@K0k)#YNv?5+Fr2DJ+zAl=wZu8SQGjDo-U;Nu(+4*V3pvA|3(q0-DJ}K_L z|8H0AHu={Ycl_baTZV0cJJ|O*#+-i_x9xruN64=KrscP$ySx9C`%%{Q{kH2(yHj6o z{#(7cz}^rRXbckD!{khE@Yx&`KJ{(lTGy91{^eArr+>RtoAUbk9pSF;f={a-%+{^_ zwuAlD*J*E+4RwX#JAE4pBR!QCb+F&L8{Bmlg3L zb)EGhPk-+JH8ICN_2c!o`S1nd497HPG59ax`0GCZj(EAoUH3kXX_n6z-f8ZZ|Me_8 zampL_?r#${ZvTG$%^Wsu!ocwO)}qCSWDDE_`F^Do{yrg7DjWCf=&IF4(=XLNSy43e zlkI`OSFXQX9{=BK;XXf+XUbonJLD(TrT&(NO<*xJofi+Y-hI0=r3(8M@dw(|Q%f9HBc12-3|9C#FX03TJ`$+oM zY7IHq3R?z-qTHM36|4%(3-1PPtIuCnTXOtT?UOS>m6~hQ*Ho*nM_Yf3%FnP+`-$yoWzWz7mUDU4rIe+V>UaIr zefjj-uS+h~c7Cd@ues0gPR}lX^Xq6A&6&~ggu~EU{UA=H=l9*EIq{#4T&g`O`hI&c z^MlXR)*V%zUuXc|Tg<%HJbx;EOOA?)wR{%(yuD z)VK8M6XtDNue4Ei@4QWCPksOU@h{7c$vmc|hHz@@-d>#(_!8MNX{WG0iSD zK2=gv$*xrXNcyLn#{+)eIUo1muk&rd0roWU>np$AV%-j33bu4p<4RDG&T!uaWaa5^ zsvB$f&fBNIPGidS>nGy=`+_VVBL zm_QBv^%M90n_F~rA5+76SXb}>%#!pB?%7d4+MaNVg9r(FGax!cP+%kYYz zpU@c?hKAkHI-_rzsF}N0FN1rfQ{$gin`&Qky1tmFargf$f6){33(nL&ieZ%qv^)K0 zlK`aM=l~tMw73*?Q1n68&8;u=eumuKC$|3omqj}-{gpR9Y$*ONZXf%M(CS**pFGgx z)+)9^TAL3vvRkX`Jq35%isim!9H;+2t|_kj(%<~TJF?=cVF_V3O;6WKK`nx`m>K#u zgzX5oV_sKweA7Ewul#ad$MsKddw(zg(X;Pyq50u@Lxu%gzgRw-1)Zpx0O_{KEY!`L zqH%|L{)E{6G@lsd_hBX7OkXCSdagV_*#GuBYq{#1+s@3G_?YW~R`!04wXj8Klxgk=wcHYra$St)0CQE$y zbwuIi#dUHKHc@-{Z@!*0buqiu`+ffutLJVmI@aL(%_{wA&)5B*tV-NAK6>}M{(JU| z3%}A+e^1K!rQ-fs(qNjg9rFVZ@A;g4u@8dR{abf^@{TaOpSvs`+r2+~;_tDk-yPkb zN%!rS>^~Tx4zhaL&N6T%|DoT`lA(Od>g`W6%Jp^b)&6w`+jMZ&o6>WqBwyF3MkX(D zx%3*RjW;bH?qIchdFuVD_d>5f`CdP=dVjk7#EHLqXZEeVH>c3vdi9NKGy4u2ze{E8 z+w?65(o(UrvZ&g!nq9De+qZ!KJu9(2x14ppVdI>Sk__(J*O%@{vrcRD-~DO#tDt>nZqxJpQ~ivzB+h#@zLhrAp0&dvzEvIpRo5_Was0r z88E9i9m8WagMZo6@~dVaZ`tjxFaBEoZms0b`#+v+yefVC!Fjt%8L*X?;?y7i2V2=N zUrsK5?&4V6jEXJ0#l_$engEN?O=}8|IsCtS{r|h`7Z!d!f9jj+%`cvP&x{jv(refl z4or^y8M{>_?s9tByI1e;Ee6?jWTWXM<9D(19|<3SDEWO8L%POwkNfH1)c3>x*1`4K z#amXhbN82hKlJGCb^Da{#bM_pew>eczGlwGf8oXKqzma){NycF-&MZ zy0yzyQn#L9-pDUja9(V&%159M|UFa7;|$D$hh=$H4>-=(L#KYCANDU<((FE^im z?z;T*tEZ7y$|D^-b;TsA4$JF^XczMukRako^CvU z^4{dVl9t;T(zbp1`8;l7Fsw}h8=Z1kDOdonQ0b9wpi=b{JYSAN?B_1Jwftref|{mt$z$d&^3$d7R^=Us-k#iF-1 z-!4sX;qjaPXu%|w4<_!Si*FmuU-QR&*2JVQg2$(wziXNL{^~uAyUhL}_1}K^JPv`i z5f6lwb}r}ra*C^qZFQr1;I996)u+CPPkkr4;rX<^>D7;I+2@_HRap*iF>O0_%0~4< z^2#YM^hKEb7nS^l z-M!P^pVztjyJm0yo4>cEmsT^}fVcE+XlZ|*azLuJ`oV0CyTVnczo$=mmu%&_q59;# z;~-=AdH&t`T5hs4!q-<-=b3F#VOz90S#1#yqs_!iwyypk|DCwBchle6)c4Op{=L6s zFWau`Ddw=~NLXDau$wRC8rS!(gag5QwcYZ0zi$oOC;!tX#(LWn?)SRY{@;(4>pqCq zsXeFVx*4&MGSsXimTBgjh)#`0M*n3c=O?WZ*|EJu{{ONsrS)%rSGm;OXWDo4mVWi` zo)q}WZwae&7pi%wQ_KE& z=Sek0T;6<8%9-+NzuAf(tTo+B4_&|a!AH4u?T?ty4^_+F%(*Xpe{Vznl8W<*u;yXI z+D(nGg(8v-q<*M*eLt`Qq-JUL1L>oC)60B&cIc;ezOue7z^&>AZtAnZee+sDUVxVXJ{JYyuksoQ1 zAMce;RD`L2>wM3OfnoFMeTS!f*n6vJWn=n-+nRTo7jG)A%b()in18)a{j>KYo}UFT z?WWaVt34Xkx9v;(JPU32?(g9C#h1cTSP5>YzUjK9cKVwCQ?D;Hei#34-@o$xZNGRV zPD@@7U|`s>_s!{IPKE<-HoZ4cty!M);871FTZ6S_c7S-rI*_05pJb@hu0HzpLywXY z|M!EJ{$5o*YULihL+13w<5z{b7K5t?J8h^Pk>|}f%_;m}^!@Ap-=@J2Ay?B2{)+W7p`xu1J27k^=7h=1I? z^!Jcdy64tkZF?K4dDwz>uf^`%?@PgTd}M);^r~lXEv5YEEqU zjBMNok;3;=g2{GBn&iz0YPk!vW=^3Ri;SCKt%xET18?@9-^$)?%p#<|x7Pfb$m9 z4M|m3xd+Wlf6qGfG@`HU`=O_>cEAst6OeX5x&GSwwSQ~bdY)$ZBL(?`iI5)39rxdV zm-3z8{%yh?=EMejD_Hc-HujQd@ShNCeE0Gv?xnw-HE{Ui{@yKh-@ksBG?=#7_MsFP z1H<;|wUv5|4eqDdmU)3Z@b>$3E1URl``m4K*nZrMY~FRCIQ99TdvgMRF;q0YnI+jc z-v`nSY*;_l_Jl4Lozf|6Kc-%?HP8a(h?jB-+rNZ|$D0526nQ=U(=2dr!y@pT!ghGG z;i28F_xhz3`*zI`NNq9xH~A=o|AG?!9#EtQ%CSal`%-(h7JM=M0hLYdJ5(7yct8fR zzhA7YUwQcZ2l2ZM*Wg*|9pg34d*G(5k4|+%G!sMrl&PRuk^*^<#~2QPYTm$`mix`U zzB9ajBz|i1>+~b~v%wl_BEEgthVH694e=xK-#%ae%djnQ7rPrMC^YUe?-BpLcilp8 zWiNb6?%p(bYvhi*W>We?$wzzF>|d-U#n2NB)$>D+{lfCS{CXd+L$+7iX;c>=9LG=` zp(Vv&fK={W(Et4pyl(CQ`&Rzk*KqR>Jl7R{r_9iUsX*DZT!`~_I8ey^XJN5fHFMkGxISc>Zi)3eD*cbHe z=?-}GFs_>}>LmJ(v2P(PsqA9Ek^izCI*Ih&EG{a2oGNG-hU>%UOaRM zyG0)&btddz`rm6#whjY>`{{k<(-;^IgrC|c1Ip2mhUtOlE{zB3rQ{z(z#_f7i85y`k<-4E|?T?`D@ zk>XTF{oU%#*||sRZTl7KlT+kDh4<0w z@3&+$t&e$u?6dpr4bt<24U*dm)+{|#%hK=}DW{#ASbP0*ym8$E$RKr4+~he+ZTG!G zSQ!6iecVs}^(p4O6F+^P@l8MOVa?OMn(v)&{%4pCY2_X$yvFt2i-F;K05qd$+-07l zy!-vL#(!V<>%W`d{Cwub$)mr&{aD4!U=#lBW;_!EL(R->C-$m)J$N2j-T&ru-OYd5 z!p9A3S|KZv3VOC4+Iva#Kq)LG?fcI-N4svLZPB-Q+vmm5~-nUjF?%Q9vz5iR8 zTt9+>Bsrd`d`j$juOD+HUgX~AXa3D_T=H-B()oIKkEb{6U&l3H_<2GMyW75gP}$fs z<=x|14|g$c@Q-od{MyhQv{(loWGTxI)t=lg^8WSyfc@d;e|yZ&xxY1V7yCPn-Seh? zh+LXb`D}syf$g2ySwH7UYc4()uuK25#>GQhUmbdS;`=6}^Aja??@s?2W6rQ`<^JZY zk~`&$1(#Mcz+Eq3EcVWOUR+k*f2+TO*Y2IKu>Fz?<~#}Q>q z{&~NP{&Zzg#kTkTm)?D@m_Kiq{Q02w`kZ=bc<^nE!j=vzMPOx-0ipW)j}&6(4BVv64f?dt!b z(^$7R_4lOZQ{Q>Vm^1v+iH(kR&v$zHPb}$<&ao(^?0OxYIx)@5Ob2>4o!=nD!0_PM zHMy8n%g2n@rak&Q_iK*+=M(qlo5{U_q)m(CQH)#-xo+K?Cr&K7GS}<7>Z$k*d2g+6 z2F;7wrC)fLp+f(6oJCRXv*s!9mWw^szWGnmGxo@RrRZmZny*8zyV&@)Z_rM?z8hrkGB^2@qs)^w_t?~Q&iTrq_RX`BA#8u#pa1T*0;{L*o?;z& zD_u49zUebUyEFRJ5hJ{*?ZR+ezd%)3On@i+9`i8KZeKKrhsQk%XRL$ zC+)4>ci{ZD81s2?yZU#eF|0YgZ~D%%do7p#PDzSUy{+%Aa;(iQ<+O#ddr$1zlX1nG z>Al<6_1@oVnJvp;r@8yyR0akHbJI@;l#8~#F`fSDU1G@j!@GVhy#3@JUXi`y^qcJtFOYS+q7|DLA1`s}<~&JUWP(jj@R=zN|F%sscG zG;}3*e!I7|B>qt`TixdGG5^{w)povSJFu&0uIx^;&xu~&gF6dnEk1r~+9s2>Yp0&= z)k^QtPhG!xnz7e=<@X;Y*YkhKy!mw<3j@QBm2bHJ1+CK%?Vq;l`WKzmQ#Ne3yYsp> zB0Q&d?pB?(&Str@b>F|d$nSjL*2DVOSEa?b=jtv#EBc)Mz{O3kBRP*pYTIg>F!ppB zoe@<1G4b{LyWZbb?TUYdW#2#Q_Q@BV&KcyKU)D((KAZfu+xW8b?Dwx^EYD5Rt=C=r zc&@G6v2XW!MVamdmhIJMWMIf!v3yG0bFRhwx39?^>dCu*?TcN9)h>&;O<#W9+>+}i z|6uM$-t6}eXY$9pZ?m1f|LjD5XU27tV$XY7_-(6K_VQu)+oyGVdQVLAe9&^)Z9o1z zjFzdr`{UT9+9RgS4ekNE=DTf^Kj+t1bQ%RR^VK+dMu zv78JHHA~-|E<7pQGv!i$-GxnR)qj_+5uNM4EkFO>+xZjOy%$GTz0J90rtJE0^R~pd zj<;t1*1hywK!5gjcs5Gb5mlW!o#B^kq@1qgPComUpqT1dZvA-A-)oSVs{R;Rm{vJk zcFMcu1>a_z52{M}Tx(SA{oNJC2_Cy|#qkCh%w83|4S^Dqz7AJ~z3tm`Dvd z&C}~`|LojUA|Dwwee?C?m!+FCZ-3dhUitW`47tP zzm70miERCzuYY&?51&Vw=QL(d@8Ri6|NQ>!a_{e|>!cZWe!I2W$5e9Z@BZ$M3v%RR zCd%EcnSXAcQq{IS_hd}(nHn8~oKYXIP^}SA#IDS5y-r7@f7+GlJ$mZj+;`9YI3fML z^TLZ?CC&yF+RWbOd;a&XQu}ScmHz$r*u5ubzG(JuyXszpUHfb|{k(PLHPap6GF$D3 z7KR_>Iu}iGEN}S!D5^5-;@*@cCcmCss#W>cdS3nAyRO&APE<}hyWf2Cly}O?*8>{z zC&uc}?6~yzcyG^E*|~BzYm8HJd2)TXt(llpd-vA8kD&|<3=a}FwKJ)fPgwgsXkE<> z!Sz$u9uAt;GiCk#-1{f2_Fmq-X78rdZ7(&u(m(5_E~$|Ft+*`y_V>5UJAeGYzc<{~ z_I^~=wCQKBySK#paSKs@Z?Lhvted~K;c3aN8 z^!Iq`?f-h&$sVaan#X$YP2F9c@>zFj_};#oMOB+!x6QfurSG_|2esedE>TetdZq8TaJwzE^kmfgIVH8}<1md(zj}{d)Vq z|E=22?_?G~d)`xqGitl%O=UPRb-x2?;qI`!#hO>WgRW`o(FVsyqXoh%30gfo##zisV_S$#fge_XhDx3b;o8IgbY zzCNXKcm45F<{xF*_iwt{Ia{1$_x}D-a^IBvi^g%`&ow^o+MDcivq~qmU&3EnbMf?^ zKF8G4H)gGM+xGcXVFoCMosnbsVzifjyuN$Q zcIl6DH+iLPo4)2={w#H~CimMq?E~s5*Eb9ON?oe9RJq~1T_BgP_r64F z7tEENpMK`q;)AP1GH0{yfj+}pMZ~mIs zld~8+Dpk|?W}(AL*_79Nr)YB@b+@f~9eM6W^P+o){s?=0-^+hEtxn+DW?tLlWoGfS zwa*LwSOIATf4>-a`qhQ>l^2%eux!xb(oGk=S#4Z2_usQiwVmfp?Q+{*AM=pZpIx8- z4pgM=($Z(TpY>A8rbzZVi`RE~Q=@m{KacDP-X`a5dCEi6*2su&>e>D3iyyB{O!=KA zmksK}^G&i9(b(BMv0XM?tjk?)-&)%zFC))QYj?R>bZgDq^}DCM+x&9#8MSZkPZ)05 z#^05*&&>98qdTN&T_FE<>l(iIYp2efBFtDcU$1FePt5YE@9MkHo7p9{K~~emm^b8a z{c>|@#eYYzi_(NUH|}2TedA`8&Qp(@pRQlho?V{rc09!W*utrxF7*TM6n<8(cL`q< zuZOzH9aWx4y6ScX?S?4RX(xFS3*PJCpu`272b*GhA*K-shOQe*YBZ-oD(g zeXgmW-SFC+U{-3ziOM!hS!xITejV~ z_jh0Twliklzt?|PjDLT4ukn$(H?ilpe#vn*i=XZPj(bN4c&_45?Uw7CtaPHa4K+m= z|LAb(q))x}J>^N&+LHOl)8*1ieWJezZG@g?iHJI3>(impnSFfcHDaJ=cJx=sJ(&9NBlz|vRh7n3q5kc@__u=7$JShoq6*End-bMw;g$|P<{8Ig}mn7`Gt2SEYHW7 zGdvTzzGvcj!HPwTk5?qgygN}i^Ns1WqAm9fcTSSKSrd7xBJKQ*^x5UEKPRSYf!1>z z5I+Hmz@smm*KIZW8Mm0fq?KUsnu22igS1F{av#@XJ+oSZ;9`j_m#gtD40~= zD<`cC39|4v+%uez;_pw^N*Al3IV=^McKFQ;G_h5lueaBJZAtthm(we(W6j&;nNJ6m7L#@P1N&-zY<&8)T5ir!lbd@=w@o>=`RSI(=>7NJ-qnsyo4xd_Or`(1 zDSPh)B}LzQY&3hF@A-{W&R$=ZZux-&66I%Bh=_d`t6HQ`c4ZU8ya#P z#^j0TCw$sjy#2?Vl#{XjFDJe}Cb@r&@uR-$Pdi?-eegZC_14z@{LQI81uBN`uJ?(4 zJuz+T6yqL|EB#W}yS6-D5`Ay}(v8<9=HB`=5A5K{AP3K$$S$wyKBZ^-o_CiH&kkD? zZ&p9|Uh36NPq#$gHs8y?=G&=hH`8)$H^1CG=c~e+=YPA8RPWfrck@ebRo`P+i3gqt zOmO>Zdb`2wKwY`+?dy_-Pqy`mlpmh^y?D*;^RZpK=_6a$ z?fceSckIC3T+Oonw30Vk(Z_<$85qS?UD*+)BCUS+)4!C{k~f!aJ|@M;z|hbMF8R{V zGq2O-cY4%#;l$x-R(sc9559Fyj%n@7*ypBue^-Ih={31CU*GIM@xt!S)V*P|%h%66 zd->Ul_Q~LK8RAN_w+Uq~Guab%zu8rF+=a2iWm}K#&nvI%jO@yH7T?@=u0#3%t-l@1 zV;0AnH>jUpSKal!X7Sp`Eqf&;_n&zde@fEr+tI+sQ4^OIZqt2y?Cn3fo69DC=VAc& z2SBx0Ze>G|y4=YLt zU8c;nWwGj|rRO*7PAmDnbN_PL!;9~8_ur_y&fD-j@|WM+S;x)03g_)jZqI%xyZ_X) zSa8BU1#7y|`5Se&5WcN9O8o zKD+z+iD!|gCh{LiioBVFV~-Cy#FpU^|#gH zZ&M<-*UYn7Ya`v6E2XPfZ12A}FUPX~`Ao)%g;~y%V4ni%}wXd_ntMYCB&_= zdp5b7-48l9Veh%1s$-8g{oJ$%6!-I{e3hR0{e+<#0|P@s5U2>Zwtl_v+Ppp0FD7>i zm26vMW}Eu*UhSpb^Y)&u{yssoZr_%FH$UClbNgFFo!R{$H@U8(*~jZne2aYldfnRk zKRUCQ`|j?^nVwBNy_fq!q z8sXO#M)Al+!ERmtfOB5wndhH*;k!s`QAA1-^*kDrbV2y zj_zxXI9?XN<#p_&we>#H{+p*&NuSD2xvaZ%Pn~W4?)Y1ko3?yDkPI2DHJE(wQJ~NE zFX0|js~hq~T<5F*oBGNu`d?P*#m|z$r|(D2di9rQcXiD%nA3SeXo%t3Qetw^x zt>QZA1Hn46+rQU{9hiJ%$LVQ?t90I{eAaoIp`4rq>a;{|GTh~3_C6|Ur}F#QH_Q#o zPp&J659RtBM8(-DS9hHWUtj;ed8JbHv1vMc^UQK*-}`I*DIv$C`r7|BTYjs~UUqg~ zvS^;FHfUpMhI$7FGs7@ z2c6rIcVz$j$ag`7pI3IiWvyshdOqN~<&T^94EL8OTimqqw!HOX+t(A%PG7R#X8Eg| zadF@-_IVoMF#^b7w$0I-{DIdteP=v0V@)4IOT z^E|LVNhj^}kBgTgLMEQw3~g*|T#<86=lwVDw`UBKmsUUMMD74zKH@wl$NKz<6U_5W z!`FVE_x0|lgbimey-vTpO;Wc@e&fE}Yv1C{{)%RP_crsLeX*8vJ7g!p0iAEt4)EkL z-&a~!?(I51|D?~-v-!G)*8>;Lo&Wk+%ze@Qe|HHUU-X)-ApPUaNwN21ZYSKF_HmWz z%`e`TmtI7q9lK#v$vg{w&=%2{wvqMJ)C!4i~q%@`D<0LEw9}%-S@iN%X#Z3f+v>_=-kTw z>XG*6^-_lPAKxzJ%$4-=^=@6A-!tjvoGEYX&+#1nov(lAdf{D$cRIW0F~4>@e*Id? zW&Nib(Z>!{rG8F}lwKTdwCjHLrM}aQHjdy?i-z@6YAy9DH&uKn3|VL^cVy!VCX)9eIA5Xa9|( zhL8TnoHFk;oU-_MU{dAH^2ViWPA-c-73g+spOW%#{n_^3YYvMoe*I6+y>BPe86{{+ z)8cp7T+>~_A=Jrk`>`SQt)lnOpOXC7jBNRDC)IXUS#NrIYS;YCwo-}EYn7RMZnFmWS2hLLJWF}~ z`R&I@8~*FPHuJ6AMBh#S?0Y_qaYp>N$k0!|NjhrVERSXNNlos2y5)C?ZuX{^(~DYi z&b*3s`xb3hKP{qv?Y5l5K|Pll{inpf_hMjRSQaezU~TI8!zvByA(Mm$cy68lV;lI5 zd7e+F+4-rhXEn}g-T6Q3hWfuluWKINDcJLD=ckI$9p@kKVg!v#u9=j7sqf~|A2EM! z%h^6|CGkI*Dgv~+#1 zT&HnQ``ojgpQg**I995BUiA37+W*;8-Zn1p&W?%sU+cbW`J2tFpUl?jy_u_fzyIIV z-{tE;*-ba8*1g_y!MO}g+i16Gdmk@Z{Qhvzly{Blr@+M%!{xwT>=w}74)e>x1U)~> zRam*6i!Tn_so53$FY3qokCD~(>!kNT>&bR2xO<}b+CR1#ov-gPB4%IS*Zu#V{_j)R z-+x$Xj`a@TnuM5oN(JkK|0LCOC@_bKPUKbe~|Pg3Z2Zg);(WS^A!-RM2x@FI@E$Ni<; z1D2ZyLQZB2{n+yD^S>7wKN#*ibej3supQYa`BSDNQ`u{CWuKPX=gM``l}Y=1lvAivGmx%>UZv)?J-rd`;anr2xwrzHDh+)KHNMbMCw zU0U5B{O28i{qO7EmA*{#OWZs)r)4Tg|43UZFe_Hs)$MWdSL+>VCngr&W!M)R!@T)* z@{z9_Ctvz|ymnQ|{pe@sp8X90k4K&@I%e^58gjXpVCD6lVT0Z&ZSS3{-+Zp~PLe;; zu=-$jgMHAVse36CZ&?}V5Bo~TrA_Wx>UZON3J zpONRJ)X&Cc>w0}ZAPgBUZ+NM3m-)c1H=8#;>H9WqSJDEjX$x;KZ}963a|^DU+57(K zF2%*2$!rbtkCtm)Khrzq-Ex7WuXO8KYrWmP@7y$6=hc%g7i)DsPW^P`<0TtyZ@$)b zMGX&zi0QRv)9zfHW)#j7q0Pk9)BSpx(AquS=OeP#NnR4Z&f8GxeYE=eIrAy+mKUY{ zta}~beSTVL+BuC|*Ed~1Q}oQ@r3ZQtLyZjFChHyejM1V^XsyoC_?Xnr*KImSzZZiB z5og9aKelAop7L(FXmX{y$cOLKZfs7Kn|NcJN96w1r=H&2m10>vXUq1A^`OyJaPiGB zqwkXKf!3sgSMu!ZUoozc|NpzZ{>Sb8eS6fB^=_sopS=5J%65^rjQ?J|IG?5(rD^%= zv)Qz#pMQToSHT}+zU0Y^^D5VQAH3Xj{=(w*OZyj>)N=>!(Nq01DQExD{l`nHyT36M zEPG?ThJ}Fv9FgKbj&G_pikRy9v(5@^y3W*XB5xV%M6Vy+;U@QYr_AZ@*Ll^t)83zu zoWJww=ey-6&Rf6Ut8sej`!kaJmjv6j-(hC(zh2k&sSRm93T(1^w@|HFz*e!NzgMQr z?^P`iUcY;h?SWl8+^${sRgJnidvX85T^7~HK1p7mcYU4oCi8T8=XdN4`46kEgAR9q zO?NUJFuxhJLqBlOe7A24)<5xkRH5Zs-F%ke{n4$P_P(3{k=xsA%lY#ckNfJZ*1J9Z z$GabuH-FZA`7QK*|LbF)g`fYJIBlJ@d;K!Tf?}`l3{p!6sb9Yg2aplNaaHS#Ldm_9E%ht&NYWA3u-%`EN&_jdA*)qVTY7`hmOHAE=|6 z;%a8GRdxN*kCtzp_gOL6T#tIW&!sbLTG5w{g2x|66`B=&$u?T|T&C>qgrzl)ZUpRN z-xpfjYxC<@bvCLw2L0KE&8A{p~TOr0?IFcbZ_#5eEjgmdHsgn8SC5KN)VcoD zy<(@+Q}};*F;uM6n38h+-%^>_MWK84$M@+e_R2B4%QyG@{!;yHSK<02wf8Pw@4W{a zNwfXXJ8j?d-+Es`r~1KW8W|e$C-C~bJ(<0+#_jin^?VHHw}u?66j#0V{`AH7l`_?f zkKXK9bmMuhW&Gp4Wp{th{_3RUaoOZ=bC$dBuiv=G_W0yE zlW(q*c3;PRVD6Ex`n&bq_WimAF@0XV4rGd=0ld6edd98E*OPwseEaP!%P=o{ZO7%= zi`m>`^vWbx1L|G(;gw%7kvuh%njjcd0!U~aJpG#K-Hj!f*KuE#m&<5#XL{!(~s z$KrEmBe`#F(siFd+r7?iZsa_c9nDMMC&vHY>jfUUVzAS$b^xuT5or#%`QXa5KmYds zkiYb4^9l7&R!1239h|uI*a^v_vZ}XU7rAl%TCu72H=sSF2_|AH%#n+#Vy{!Fc& zsk{EL^aJTRp}X`07tOUV|Dk=gw9jstMESEyiPLtwUOd}XSkq_6e|}${Osx;&ovWA9 zA6@^M@Y2ri{WQ>EK0~~6bwDH&L;tqr73&N1jh~ffFxNC|Oxf~%gZuNIqGdvF|G8{@ z{=z9x$GCc}a`BgsM|NnR+rRjJ`t_dVqA#V&(@Kw)K7NrcdtmO?`JM4U@4jk=EL?ri z{!1}cnxSHk?$PSj>Y1kN4@*6ec3HS&Z@aCw*Ot#u&b>bxE_PV0`1$wWr%LOi`t%gy ze#U9|GyTz7cThDRRNOEyFxY8JU(jT1IR5*z{m=FPX8-?P|Iz;6|4j#1>HL2+|9^Sr z&B&CtJ$%^>`30#j_dR)0bbjj5v%8f|KCyjKZpPSitm~;&*Ivn8K?V>mRHK&W4N!-)$!K$ z&D6j}w)XDtA1{|R?)m+t^4YG!x<0*?^;PoPcbUI*zApXKbmijwtzhHrPqLMG?K>O4 z_mh0w$#0&XGu4*;?+$0!!3hfNYOe1SuPgF>;I)fg&^IUWP2i&1YoKW$mT#5DbK=uw z*w;z#pZnN-*3(@JOZY#;{kD8z4c?|+FzZ)h%IxLci)#E2)~PIQ;rE{?`#ks2j`+!E zFF(6$^uU1YyHWNxv3JVr56e7|J`=D@-*VRt;nTAhzNswj)4Sb1^VA8hg!$i}JWgDe zyW7Y;-aJk7_H@t+yXmp^weP{n&2OUZ1f9xNpRT{zoNH&q^~1)oEN6S*uIFYV?^3VH z1n%hH!&lmnZx9SBwacpe?v-yAO;vuw!~;amYivPgZvr_Z{p3mECnyGJb#lvOg&H z`HRO*g`4MjyUR-#%GvzgRk&p4dTIAKdB%BK>!#i`HF;zO8s}n=(W(}R{G<~8C-wK% z1F@;ci=~%VFI!%qzd3D5!TtwyP*gIhJ`^Ag< zIgi!eHvF{Z^|6clmk6in-kz?}&GaSm*BR?y;@}8u(4TJGp;Otk|Hs6f+w1u5gqGQC z58RcW1qxv|?(hFU$N#E(o68X+W=Pw>TX+15Y|DX2Na!|C@)ZUwWZua8-=@Iwl zo|`@?e}A6L-@YjX;wW~^7Gl-af{x3t?Zlq`%UxW)88KU?JrqS!f%lF zRt_{+%aE}B&BNrAu~YY#*ah!!`+Hi<>(22N{g0Pw+%=YkCT_3q4E8&dU+#M{+iOc; zk!{eP_vJe0-1_S7OO{VRR5{z?+5VTiKKkDNrRVjX;qkO}zt^nolqmS=Z4P&?Un9W)LcfGVmq5R@|%e@14rE7sA;hWgIsqYx;ik@GpRXOG0 zE?%FeHBIhs_^RYc(B5R3Se3Lpb5CENxVV2|#66$qQ(qrjZ~3d33Dnv739^h~_f~$n z*LMQ9$?m-w{VP`cVez_c1@eK9?A&(8Z`QbL%nS00y9n40A=e|{te?t4!Uu9HnEQM2 zW0up3=HJtIpZ@l6@V)G%)eUc7oo`xfZ_UWC&$YJy78`id|LKk=d7B=U`k&MI?Z&vj zj`!T=FZ;9$<&|%5&ziYA) zk5m?hJ-SnIsPyrR+=^T2pk@UF!~7|CSA$n4g4Ryn@r*8d?|Y_vSw!8GLzc&H=|^e6 zjQ?o?_Ji0v<(oC7am!f_m{&w?G%9k_{M{w=Rg##Y%d0e2imFeectb$+;v{5BjoEU_EaRIBTO)eM z^*wLNTx+p+$=1pr6>?u5jG6iD^6l_{UhdoGx!Z8`{Ji~i+s)rn)kp0zw%5O}4*vG? z((m|hZzsO5|7MkY@9k^Qp!S~l_kFuJ)u&xp_k3EZ_U({qf}iDo-n$Qu1!GVw{Igmg z%=|#wW@kyfQE}(B_?2?!FTPiQJTK;4-;r;!u}U^Y^JQ!KY7Rd?aPhr5gMIz;FMoSB zPc`iR_4#kl8Ix~}(O>6jXZJ<0ec1d)MvIXFGOV<-dsoS=$-8YUr0(vss^;Et)9Ch< z-^E`TlX7masOZg!te*6Cxh;R%y}julKi~VVb}jj}p}^<3TAq|mmACdpADeVBd0omA z+Y>3Lb0+5o?|HWS`{%n=N6&Bm>9y_M>Dz&zVl?cb-6Y+`=U#td_`!BRU|Y|ez@WRq zi)?r0$j^)0rXTp~*B;yB|K=8b*|@EI|4-GqZ!LdSaesKb>AcDPi@%Q4MRDzaCCP4~ zTz%a3!Q`srzplw_7cyRcwX_wW$y?pU|!qZsKA7|L7-ptOgG%)_PPgk}-eb4`i<-aW^>n=aL`?J>U zX+c<3XU%E(3-3>T|D67=N(c58I{x)1}kw zl&8Dc#H2{?*5&HYUwrQ?ydQR>?}(Xvtje34{qFMb?^oTm__de$M_FO)*>&3muciK8 z(_(Lxos+JzI9b=A``*X(Na>xy+$6}M_sqL9NtG!pPwZBScALFePHElR+c$qd?YQ)J zTIrcNe*?aC6;*13oqFZmEqCSLYP0wI?)6>#de0Vnw`s<|;vk7y8E-gQ~XCZJv087>fGewFQyU=U(Xv%1G(@1yrc7N zw_Tn+aiiEb?d0Zfzw0*g-+pX)dY1Au#y>jN?Ovcs8>z|;(aY!7*vzoBR{pIx`~Mjo z+bO4(*&0P!R(?6?^KJ9il=p{2`BU9b-MIND_nxiKnP-`w^CXYn{JkRFEPwVU-@V6! z<}Up^(RcThiQj*UfvTjK&>j6Lr`LS8ie=b$(0uwk?{hjA<@V;hzy9&}ltORS-T9~H zu9Lo}zqGnxv(8_q>lf#zw*S5iI%?z3%m0tx|5rQ>9zv_jOZmO}$?aIvZP#bt+bGtT zxLRKbW|-c-oC;Wzmlo#=UQD@d*2IF6Hw+?=|kR z-+KS=|6UsvwLa&T^5m(LtG}m9-2AR;=%1S+xpDrj(!T%u%kAAa<(^CVye)J4(y8DE znA3Z^Q{VhMe%^n(cxm#6sb_C5%L)Frxjb@`@9ru0zW;!DFcRd!wV$n08TK^_eU~pT zv3zl2lb_D(+5f#t=f3ogiFfGyz5PthS&ntm^=B^r7HhCSwQl#smHRhq-u2H+iFwxb zrDtnF&#%4P?|*+S%N$dD#He9@>hl0j28IIXo6~bwpQc&Isu!YJ@0PH->k`w zj81!B_4P&MwR`L_%KT}Y>g%URz1f~KOXvN)?e|jmT2JfBxo7tNTdA4N@uoe;!ELruQ1ah>?)4VNA8dgdnfw3O6sPI1Yg+uD zvywX|{*d9%;xucusc-9hFWDZby!K9g+1`Izcm0=_-^tefc*g#i>9k{Jr_Lws(mr{=KiSeolDR z#ooP_Kdox{>dX74+ujy_d3V{pr(fp?->k{Kw>BK?3yWX2=b!0k-k@jf=;sAFU73Pj5^5ygm3$+_~j_{?~iIf)@Gf|E@E5``v8m@64W_gI7A! zzZ<^4_{wl|d9w7I;Pj zrtzjcZ~n!vzUZt^x2%>g_-Z#Jb9338qvvnEeO){E;@g+8?{kV*kJba*n7Ih)^^vn{8oKd`@Qa78>kBK2Q^V%U#Xny#BkpMd}boIrt$vs zTb{1fe`HZRe|2AQ>hFCTcbQ`r|Lrr-UHUuo$mSn8@oQ3_-25MAn6@ExO^aRj``v%{ z#alnyIt8U9ol?6q{!|aYO;!HC?Q5#H&C$u;^zwGkn&|V*C+4yD+n&DpyYBlw^U`f1 z^QyP08ogSP3@TG^o<6ZG|8t(K(fe=QOeL)x;pM@H4iRUf8L*ZxBC0ps6d<9`_7)&^>t0~`NPNOJ$+NV zzxw{{No&_n*In&<-Y)a2-H+Ps_iozco{Muc`zsnb*>}H~6{z1Zb76t}$+-3BUWYLK zVDtK(+?n)eqUxN8X`B8=oYOJ9?0&Oa$6bE&I_bxjn{7A`R6hEwS7fpDw`R)k1l{Ok z<#xKqrYD_I+higq{y%Qn&n=tg-Q0ET{I)*_*F@o`Fby7+L^M)*Q=BFxxU{Qx%=YzW%--U z>Q|>%f4}!&de55hv?n)i{fa8INjsgrx%B7FO`F!71f{gUn<{VV=q@gopF45uYge<{ zY4aY>lm}IYXF_)Lr>u{DUh2hI!@Tr&?aiP?y|wEscH4r}+GvL9FI@V&Us}?<>(0`iswWm_t{G0eR~!AMM>tJVGT%D8M$1}n)5}|1 zUfXS|dw*~G+TX^fuRX5&B5^`yV~pc=T`6_r0K% z<_jmXb4|RN_ophqV*87|oc%ASrCi<~IeY)r&3kR%2c9o-yQY14_y2D)!R~w9vp1!t zMo#v<|LpFs)X!TYqmI3>tV_AJO?$T8>1*HLtp;VNnQK7_7F3roLF$s~lM!xQTe>#? zHr}+~vi)&Iy<1=M{w163y}mQdzZy4vx*hxUeaRYk?ftj?&EDB8arnfw+I+XuNB6!K zJpEQSI@as_j(ancgKzFTxh(r~pLyzKoz%?P{Ws=EZtI!*O^!LcFT(Bhi{!+T`#Cq4 z-K+H4lx&o8IW4o_cb;nXd2a@WhS{g~Jy@Um^4Le=D*2S0i`CA1%arH%-cNhtwky55 zTF&f?PWpS^e}cc?&%MWP8Xd139X8GM=9k=iYvaxQXD?0tvX4JKC3iQdgt4>u?FH(j zrs>>q-*kQbXRECY`x-C({pX^%ZThD-|3HcJ(%*kQG3J|ors{xvx4C40VEpXI_3LZ> z<$m6Iv#8{M;mvRQsjqjOn_6{TV|!YP&9-u#7b{BRqPO4MxO?r%IP2N#d~1Dr-?hJ~ zu&K6oKYpUBztPlPXK~pzJE@!Ii*Ij9oolRoTC#Ti&gpM#^%)r$@>YFM+wAwv;jwU) zd{@rJ-SJ7gRBwLCz4k2sYfk?8-b#4vy{JvtSQ?EMdv5RTcKw=j?%Kk$_mV$+1gFiY z9sMb<1D=;wG1fFM73%ro{_@`~sK+$!#?LlBJ(*!m^zQo{w%?V#zMnS&wP}s`b}lKe zDax<g1Pwc_~k7pVe*pz6X>TSFSbRyO+P}Ti5JMOQoORYmbV)_O&B5{qpvgwHg9d*H%71 zwkO?E`SsM7wKF!jzu$O!v%k^JO}F+$mw_4xzpZ93KYPij>ifle->XBm@#U_ITOJMS zOc@tHw41cneeQKlHc%n|9o1LTORF2sTRqx+c;S?H_N&U*EJ{h+`#65$jBT>+J@v08 zmup8#x9hlFTd8twpl|Oo`n4-thaBz0W~qOKln$^x;ntS@onqYYR{l52Rs;=?r6&7imo^Gk!zJGJ&tv$D|-?YiS zHf^Hs{bkR0SO#t5%l#(T+`qIye{}&e7j<1*ey>WeR(gML`;GH^Pgj-ewBE~&E1}=}jp6y!(rkyZw)qsi#kFQUh0tvR@}GO1G@OfBjAE znN8u_&gd+zy8i!e-Kl4p#ovso?!7-Tb<_3y&w2Z6R6%1O@rI-GG{zA66^Gi>0t=IbsZ+}m#FM99qR-C-}`}>K)rSnqX z%JPHO9;V1&U3qcY6!W{AX{?&HidLV5|w>OJVpf(4m?S22VIFFE#JDZz^5& z+3GIDmo>>R?Y1pe;kr?JYJd7?*@+drcG~xv-|U_E?}+6k?b-3i7J)o&dtcGb&ia1P zuKz14{>{s+pH`7)zj$Kd6+uu>?d{}ye{b*3ubzH=v0QuPse2z6E^R*v0=gc!xHec($Cj11rd$@Jp*RITa zmRXkBRrjaweN=mP+U>NG>P@LzUPh*w#n0Z{zxZCQh^5n>d%J5V?=71>u{in2?!SfC zUcXyZpuhQY>%ViFj2kg2(eOo<#f&T z^5c;?(bm4(=9n!$p8tMd-S&P+(W^Xr-`N%AueX-Te9H@-U4Fc5&w1P9^%}>6_Wmh< zuYNvo|NC6f@Yzx3{Y&qJR$n%EzLsZGE${Zl?ukWF<;|L0x4c!=_h!%g`l|N8hU22^ z1HZ^^-pg-aHU0YMd#RtdR&Jk@Gtcb(w2QlaZI7Ry^{X~Mc>lAYs&DVbZf0+NX&a`u z5ajthkmvJC1JmP20!8)yJDRrLIdo9dSeO{pCqHmXX(F3VGhd9us^$p|ad&-P_Oi zN>>?Ql~CLsZht?6nSmi*bGM-O&eiwWHN(^1rdi5_YRKLEHc$Fp&OWpE+tyy&JlB@L zt7`k^^vI3>Zti~eruMv9d}8ZGCbElQx z-s1YcP!ckxY7Od9{JL5>w+hswsH=B7wYgl2w?F54hlJ0Wxb^02%J%v1t8b|FZQUW( zE3!CQ_FakHR!PhKuqdWh_gMG#C2O;t+xGWcuzk~8T>fwI=56QQ zuV>~)GB7a6DD0Nh-nsc=dwlxx=WOP)^EEftzHRQDIQyM8_w}vw+V$MmMXO8;U!Q!l z{OiB0@2M~D&Ucrr{GW8|&ENO6D@0Fjvz&bPwpv`ZFQ_Uz&7pi-T=RYHwzW?sI#NEn zT}!#V{pHPNf$4jWF9+3V#n(ZF{Bf)Gm-!z^%fz<)Jk~QM{e6It9_x$Exw?7UckAy3 zF8#f@Z|mY~&rgABZtZoM41eDAoPU1y`e%&`=}&)^TQ01apBsMC{N%QJ&Ga7rH-e8@ zZ&q#VH0;pbl)5!C`rEAt-??#nZ{IfVyJW1l`TO5<({@f-yZ);6zAdRaE5HAZj{YWi z{J_1`o}PoUGnLQvf|e8bO}pDO)#l*xyuRCCY#3hFGHfx=Hh;G^=bxGF?XWL<J1?g0Jr}giM#8oS z)R(jtf2Vx2>RRR8CP>WB3)v-qbDDCFX0mK-P12ovLU#jGe_xY)x^MHMlKNawcdhhV z`ln^@S4DGwS7jG}zxl1!Pqmxh^ixxG-f`_so)CT96x0+d`Z6Qt{kLyDi@)@EgR6s? zspku?TJPKD`u_L!ZS(s;k^K0S;R&nk_u22wTa(!M16Z0j-Gdt0nZF05xwvf2M>Dg27x>}K42^@iPQF@Maa)Gd|eJSm^ISpKrG zQUdo^bAs2uDV_FdV|zB&ccbigaiE4*)bmm)CQzr~ucYSxBU^47rJYbe{YLrd8vk|b z`)@`qv0j}M-~IG_&Ah3IR_1k!-^CSAK7W>ft9xhqpLgHu>#wAHZi`jDpHh;Ow2Ud| z{g21j3VU-RBd5vOs$Jg=ZqxtXS8k&boi@#O{pIREc^7~GTf6o@I5J~Q|IJACd~fCo zT5|4C<{b7(<>Q4X!se$w&RSe0&(fQAdRNNn%+22#mR$WT*R$q%;BT$!6=g>b&t2;# zx1eXq^M{Gr(SEbbk2{$8&z|^LV@|Ez{@dXtL9b$NC5v9ndj7JutwPd%Np`Fq7H z+3?$ozW-WCe|M&dAH~RlS`Tu+K|5VojrSg+!K*jjE(zxZykYarI z^mp#p{&T%Q73KAPzIo7!-%mc87JrNbH@KN&UY|Mt;9d5wK+t-a1N+O)=WqI6u3LR9 zz3+h|rzjoEMyK2{J zUp7X)ud|PS-+WLf`g^KwbRINTZI5T2+5PnIdgEWeZW`Tsb90&L`@T-)d*2fScbw-s zpj3Y94al4KO5>KRz`dES!++FD_pSEQ-{)UzUOM{Q{P0Fo&@#P$w=_JNYK}?HKYaK4 z7yG}5@Bj4;n|FVX{OM}lS;;w)-8){*tnHE2*&Fsg;_|do+oYR@^Cq3Wye#~^)zOmo zU-mo)*HEAdBHP=7FK?=^Ri0LR#Bf8o$pnani`Vwu&M85{H>e}+HN+vyGJ@zl?~uE`1R@uV-QF^>LOzS<%$Df-ph z&i}Hls}^%#{}@#on7z&EyLpt>xub>#$vZ2Ly^eXaI_~BlqiK@;-VOO%zQ`u2+?~HL zv#>5buKStAUez0Bfj)Md)<~Z|b?TgVpN3m{)pvKF?N44L6~37bD(BxH5BB=L_jk(a z*D0q@ZhF>{obcZ0+#UC5*^Tj_+nNH(g2R{?VmEzSe?qos`Jq~s+ih0sgM}VQpNVkW z8|`*#GiVLAe~NddZQt~JxfZ)*;=Q8W^`{r+ChGNd-t<1ZIrg>NI_aB#jKr2!H|*SU zU)Alh>OZ5}usQc9UteSymfvXWeP(y)l&1Sr3#NaWR_@My6tqEB^iIE?*!OA6uU}hP z;q}rk<@8yjMc?;Um*0^GwP@Vl7Ygq@CHOma{SE!El>+~NcRUtkV0bX?)$EOC3>Q?j z|99O{EZ4f@zA1ObXDd}kaG(Fq))M=yY|B?KChht3=K0m*hc3R)@Az&pP5QaF%D?iH za>jG(m9O{a)Jb-~W;-zF(PrD-dHcPum;U{5>2Lqjy<%rKx}DnTt$Fn7iJ+g>|7}MW;19vkNcE4)!&QH*J)OL^M7*pmwX|Q z%*LL@+m?sxq~}Ju=UkS{{;gbRR%EI-1&X)OU`rArO$IJbFX#X z+*|eCd(V2?zL&T6@&r)s&JTLWPs%f6-wW9p2toL_iH?Dy9i&nTS+ny7j{$$VUvqgM;YwDrxT)C># z-|x;}99dbH9oPNDX7AF^+MA!9a4LMiEk1O|y03ZZ{+YYVBlq5(xi@Xw9<%JDZv@@< z=I{NjdTI0gehaoftG=r5flHp(u0LwE?R0!~e*djyx|^<_+_dc<0|P_<DzC_8qrEb|mxC-+v`F&z}qUAscyrf^M?E$orLVE!ExnpBkJt%}=?z z89W|z;P{`X^Z$hZKfC|m^7?Q4)6dI2`SWLW{lEK5S$|#p177p?)ZTEt^Y>sku3h0z z=H3&^SJ}2!*XQ_u`%P!VMb~BTZT&E>l>6qM$fW&OSf51HExEVvjp^%Dou?;~insk) zIwf~Subi`Op9NcAgj>hkv(KWv_mpj0@AOA!Q^~y}=cmbR^?d?(*=T{{JWE|MkDw*&H)n<4n5iuX{DYpd~;x_L<(E-`9MR z&^-IzGtA+fOsvkkTKm4Z{qs`R&-A^XwswZ?t5uInm-N&gF)VmeSaQ{_Yt7#6?s=Cx z?p^QI0PP-)SKV!>#n_;C)4_f28Xnyu*G-Q%`i14px4da~rE=~hNL%n-*e-d^`Dvf! zXWo!Mwc)<%z4wdHH%}}!I8z|kx2D)^f6viUwjFa0{f)U9wden~L$yyzIqIXPl&sg< zyngBRo3C$0XYjj=cUN;?R(?I>?0(go*=d)zMrxwLS1{?`8he#ulD$PHsmiT*>A7>^rqyg zeS4o?*6!!6|N75*&d+#13C+;+1$TpY?Ke|C&VC?|>wB>p*O$FdfA9aX{{Qs)AL0N1 z?DwAj`O6-@$B{i#Li^8nH14rjU17gVZ@!jy|C;B4tZ&|!Zhm=djp*j>R#~%eVAa3LB|r7cyVb_qU(WU4XGV;}ro8+9Ja6gmxcM4)-9_HdS6-(dF85g( zbTcttVS&E?iPr`5LPf&+wYIt^RYrH8&HlFL`MpxBU3a8kPda-)?X^|@P53v#tbOxr z1q;Kj6lU4dO-5Tzh+@m?fxGP0PksOXt@CmEl8Cuy>r;rC^>G7?Vl?{(*&E6CBfci&&ee7@B=_pYlq|Af1}{CBHIMw;QU$h)VW zm9p0u4s3j6c_0WhKC!jgvXo(gx7me2b#3SFn2B#H)=WmO7iKJssQuo<(|2^sO`~-_ z`9DjRB|HDDt1H}7^XrG4W?1^?_?Z)5yP56pNqusILBc3#$N%^LKimIr|NplBm-zn= z{QqCqYZhfavgm%SYt486X=w&C!||;5!LMNpeHa|3rVy`LsmZs;QDTq+@}FD*OFv&Yg3jqSba8JR8zkYVR7e0RC%>E2PN!z}3%etxHY`38sltOe{ ztIr={c#wSS_Z2J9O2C|n$YtJ^rQdGC%RErgvTo_`iC^SZND}%*vy`O+q)ZgqnQ{BB{v&f9;i)!rizI`__ zDmu4y)6-gL^Z$kmD3IqJ+MAxl@WBuge+&%UP9WF7uMY0EhPQ|{?!G@i*Zcd#y{T7b z)E|4R6}acU@$cUfonLPk?>YB{n-$W&JbmxA%!gJ`;&`Kbr&^7TLH;s0k1#MKbR%cE z<+2B6+rsKig|3db4*fl;OKhx@S5~I{z5ZkM_wS~U3vZ^q|80Kz<4W)#N~0nu9Qriy znW`>*WHxGT9Ji}qCUg(K@$~ni@9WFjKil`6Rpz?9d2Y4x^B72L?FA?`JX>+7R%Q0` zbFV`fV7VII#TaITQkrP}Yne!o`8zA*e}4N^JLkKxe7CCmUVme`en=do=1+o!yL|Ka-E zem#%#HE()BQ-$T9LtZj46u%?E$TeWUvUUejf!o&A&l+Q)8c1q-4~Su9V}+b8`gp z;f?U!H@#PHezJ1u?-LhOzufF!xqNEr`vN}s+MZVHIqBeJ_|r692Ilb11@b4S1wSvX z0*6o{D1>4j>DN73U-!iRk4JT~(0?&|<{eX!6DM+1Yux*uvc#sk-Kb#N;?pmT<-BH_f-4xGo z##_H{S}`yja6TKb;M~7ewfSlK>e_ef&s}@;cjECY(~_ef{%gb)<(~^^ zj9Z&6-E!9xtKWvD(}Uj2o;z#tum4~7lk&Z4ns?uymOL)>K^2t!V_<9SLcs}UYbgUm zL;J6i#w!1Z`%<1w+H~n}dhMo)`@84gy7Mw(K`=_0i%78yMdi9X=S|JmzB^q8Z=eiR8pX*0dnvRb4-SZDmJ*qu3 zS@(79yAmD)4O~(D-j(xHrt7DRHN~KB7dhfp(ysnPfcw@12M1_Za)&SOl#0z3X1Z}phZDZrE$vz7z)}YOR$`SaB{mCU(KQ+PyyciB@sR zV&!@(^mqO?`zq(2du`kEp43kYD2*4h4)bE@FwTwiZ;QSJN=nLfXKqfl(M_LvA>D4- zqrJAfbZXikKkfPcOZwboP@DI^aPc$!^$hWDpn{VJ57k4GAL_aS-xoze59eKsezt5SAD;X5@ET8_~{nTcEec|KZ z=Kc*|m2N+;PlNCN!#S3p_x8R0-&VXd-0c74qNAnEKa@ZvOd4oj`1+d9R-p4M86JeZ zZ3vvZe8(n-yT7B}r+nTrv-Q&7J=?_IM@LUHzO3I?(1@#;m3e;h=X9rbr|_U&9Ir>ap^h&pSZt$AqZSUR-9lw69@~0E7 zG+=Rlve%aTQM>wcXFgpYa=y-Ht!PiOeA?$TQzY-bzR~V)U0;>@`M#-fta$@FsP;Eb z2IY#nxz{sIo4-}NOw&I0z3Q0c>n}aA=M+x$dx z%DeMROX~Ue-rk~KeoJ0P2Umf4eMfa?8K`=m^fk(D+t=VU`*}%!-xZuW|1YloGRPr* zo!_}0h=5b79%!A$iqBSi(=OC4KT#CcxT}25r`R;P*C(%S@4fW5(k^n(`DwrP%B{sf zvrUa}*tR^;T&EQvA35cHt%2F$X8onVf8Y3Jutt63`5#HRqHxExSnnD zWnbyuYR%ulJ1U8J%4xM;QU>mT9U*5$qn z+j4H(!m~QJw|up@z58GEG4uCjG7Jn11;RIdGfoEE>@2Z&zWB3~^{aYb@W!VHuk5ZU zXuY)8XEvxG%?oetF{~>;4ocf*%g#R**I&*%Yl z>a0aU)4o?L=jIt_?9WfJ+~iREq{4sN&0`IlN^=&M)c4-oc*Sh*@hjJoZ|_iLU|?8& zD$mV(-lJ7arQ6P!Df7E-`g!}zhY7AP|IOQGw`SGjgQEXE%`OBk*LC{8yMDG@aQl<* z_5W^v`u}+U|DUE$ttW$qK%u!#{y77~=NWrltb^wDW^PVBGb#Uv_SIA0_dk94{_AAW z{DZOUO}QN{AV=;7Ida$4%DF|x8T*SyLkJf(rvuCY4?`hd%WV@?zfi7GeOC{FpJn)~#3e(n17n)&ZzcK!eMnytVP)Q~FI1ubz} z^V#Za+J!pylSNl<#Q7Wkyqlf+dCSa98;$7wX2v~p`%Y|Ic=pWgEv0J;GiO)zy*4Ya zKF`R&uw&z!V_RUwR(!Nv%+;O0zZEY4)ppS>`*xMYFRJ)o3mUwGH9q}f`g8!3>0+tm#sbe``e9MyJQ}N>f#u+-G-3-dDG~2kB!L5XZgm5ou{q- z_I~QT9lmQV$~NUnoxCd-Q?v-;$bTU(?G{!1k4150vP?tujD;@M+b`9wX@2x({k({M z-{YRYE4zE**?y~y=NTZ5d05UaXIJ$vHA`Jn`u-0nYCGRN+ww$nUd*oi|4aweHSXT`fB)^P%g1^3Q+P|RWOiSe zuLBxs-LH<4)M}Uu3FJ+%sF5mvb=5$5Gw!GXa@9fpD8Atyw?|htndXI6> zWM&4252>4^uRscs&jt&=FZ6ityiQV1EoN)|`Y%UvwW|8pPQ3FpQC=NXJ8p4(;XnOd z{r~6o>-%lY(OqgkU+ZGulyv*h7hwnFKEHJOHc!K|f9GLP1b2a)`0@U~^!jhp>wl`( zf2&XZ{z|F*1bj~Z5znpnYLR^s!IninMzgn{)p`0X=kBdLxo5O)x0#jSS~f5FbH|;x zwQLLw4-_}ezA|a~jFlzwFBg{lj|7dL2ky|H`Lt169yHJn3QE1Z?tixaevjNquamjW zaOe6})mt;YzjNKVeY9?O@XveId+(~>dp*%B%~ zp9c5$9?x(4{LJ!aM}msJ?BeZ4pcFW7hi`S!_P>k_3XMOI@Pjiqv`o}l^?Ojw5##B%q zxM1(92X;XLd$Z;$t$Tiahx13fYZL#jt686}bN9H_&O8SBuj@fmKIh-a#XjNN=?t1_ zuAY1SrA70%-Com_xenhfy8We3@mo*+xuBwR*VdlB%XRan*_NHR&A#>(=Yyu@YkbNU zM|~0r7hO>uzuf)(;SNc!b6(%SU-*3COUIqe`32B?Y`3Z;Uj5Yfn}4!TPE$Vbg*4<* zCv*M2%Bj#&&XAj26_d2RwrmF7MRwjW`DdKxFPY7;=9_-@_q}E_Xnpf9<_RBsG92W@ z-ykPmxmk2uCURH*8HMQeW?tWmU&y|e>n-%y5oW_40q$A6N!$8F^IgQQ{lPDPcd~w6 zo_l%Pq1rQ>OYQarK&p`WfxG&9#NH?W%CSTX$HKoQxfl2KeUkg&l0GfNo$dIvb$i`a zroEM_TQ{@o`@Pr2Yiq-;lt9BK{}w;m#pt8D+g^O8EqsDmHvK|f{>e?{GOEXxZF;(` zbK^QW_rCmjy3y;+%B7c|+P3g)?D2b5{a5eZ$y_5jPsv)`8#KTV+P^#R(J7|VZFkIU z*B38mkMY-D(GF_{2W`{+eRH?Qz50cavd3<9iTufF8>d`MJ^dFv^9QT;j(-*YI_)^a zyjN=ueetOLwnk|E{-D~m{?p#?y?%Yx!(9{4?l(X6?ZA{xzl$f{EQU?rUoK>O`+d5O z*Z140udlkjtkc{0?xtDqu^Y-YZe@1xJpr?S4v*_m4r{4z5ZlKS(H8vl*xYFo-rD(g*1=$| zS6kI{g4aZEJNJH)Y`D8#cRRFXvYz%X`BucYOP?M;pZNdB`_Di7&)xcUWPj{Vz3k4? zj?$Iab&IaecT|AiY>nTA6-w({_iNFA~@09y_w>YgY00CUDsh zV0C`mYV!lnQ=X^5PtFgH`MF`m_pR5yh|Ni_*dhAvi^00QI>;F-kQgxQ5-;7-OH};&?)Z@SMBIjSMjadGD?=7SJ9B;Gx z6HzL#zg6zh-QP;xxDpur3tSSbp4{5`-AXp_z`XO3N2}+bF5dNP*Tl2-lPYvms~?DM z`u*AJ=4Y;nn2h3o1fu0;Es`(jt}LN|GT^u23A<+;oLO50mMDt))dckk6ZikwfKr8S zv`;T9xwx+{N$kVhZBsJbPi>A~cc1IVccJ4K=AF-+fAssNr?UjXL$CY0U$Yf>-~3xS z_f2K%iJK?;j#}L;UJmK5zO-om_S<*b-efiR{=`^YwQcU(;&R`asew8W*8Q807EaE+ zoRMbn?}2pc^JGp2h7SSZ_ByX>D+KsGlJ0g*Z0Fnide+(7Y46M8ch*4 z{E#b*H|KB8iQKY&@e6i?I8coB#hs3w{yzJ3;S!bF_;gU2G5wJRlbz-|`$FBW>_^Mq z8{qWcFY9wb-}TZ@BGss@#gkl=$GV4 zuMg>>@4Vf*zJsWHe~p|^X1uifeRKDf_2ng?br6Wec<#}vO|_kq#16=Rod$}wz1K^Q z-h5tBE>pRF{?nerMXO?P_*CHcS1y=k&b4S9bT`dUH43M@#y}ipzYzYIc>( z|GV`2y+gG=*Tt?aR=B^}SGhjXZnnL5*u3|#^XJ7gy{KL9wruU>wf|9zlv=CF+TGs+ zJ2V0rSbq4%2yf0#JLysR?e5nY#@{y|?bV&X@!s?2Rpnw%H>n@1Gh|nthluIwXV(SK+wQ}P-=C8S@!)>uSdUT)V)9X?)>YN^V%-w z{K|B$ot$S4oohNSx#fDFPWAEhq#o7iyxI2*kKWrj59G0D9Us?gr-BPl6Qh4SRm*S7 z7o@}d7Rvg{=Y8|4c|CRxC#G$jVtc!8ds9`vb?)VB$!}IX{(E`-c@fYZY zPySvpsoK-5es0dZN%I!fsLVD$y}S1Bx-a*B$K}@gZ@zA0gj`o|^iLN(x@w-#;fs-4 zC0Aby{og%BUCArqfbh|;YxmhrdmX?2jg;K~XR|oZZ~eaO;yLNgRHic;zt?S^HvgUM zW}G#{SEbYU4A)H1-R|4t`1OV4bIYW;{S($k&yKvdH5?RuTYL@wP0u}iap&#dJmpj8 z)`Rw_ILC%Ah`aQ8!>ibBHWm_^CGVkm@Vn!qNxO3{Ut6pNlgj{k*xy`{cAh_1Vi)&R2_mzII#cfBoLt#kV)AqWC&K zefrex=X}E0WMkQ%8a&W^G%F&WdHb|?Q{O(U-g1BTwrTHT%^PlBy(1MQ>e|p) zou~ADn#;xNl`rmX)>E#3YA3XQs_ycXwK?;A@2kzu*L}aO@a=SlZTrHrPxq|ZtL~1{ z&HwgS*5zTA|H4F-nK`@4BegoG=5{(odH$IlBx$fY2vo*SdtG*hyJo)b_1^OPiy6{_ z))mj5ba(#slk>tm=FEK)v1YPu`7F@v@y|lNbC0HHyG&9(=2mrVT2;LI?CrjL?(sl6 zv&%u9+0@xdwwu4kd*9r4@8c!2^qy*_`YChEA(0!oAWrzSeC>@5<#X>%UD#jYQT+8^ z?RNhDin%wpzr6c<*A(S@?^UDa+ve`{ivi69UFQ206S}KE>3h^c+o1nXzb@Sq?KUmP zvUF2r)pu{R_fg;e2pyRA{Z*GyZ9YmK{eS=Dx*+GNyS~W%-<`7ZfvElIX`81-l&voR z`}!;MjFTRMX6q8WHXnFV%KSjD>izpmDc_%$Zh9`Nl&)?q_B!SKT<~OV-JI^tKb}fW zd~#~z&ho0`v-_m@Z)-ubymK6nUWT=Fc$D1OjALN!f+;B`0drI)C4dZ?HFxe7W!W`giju zR@^O%^>Xw6e$&Wq=9`K&lX)Q{tbZ!_-(_uKiBI>p+&ks=>qFJi%lhk2=V;zNZnJh? zk^32O&DR_Z3Ox2Cf8nf#m2wmz!;>DmN373_WPPW|8hC$SaSe=Nk)Kb|&KIvM!%f$~4E zTF|EX|60W&%aVVe+VgRL|DW|%*I#d)viS7%jeDwQCsp73E6(C}<@LTPk=rw~9sk|U z&W(L{@i*s*^IJjtZXnA@wlv#$eg9cjyQ)M!^ZL@1Cwosl`;XF8cwAf2baB(+<*qwz zukB2EtN$R~^~c($eP?cEU$57+Uo?fEfuW}PllRG|aV!2zKXUEe{5JgAb#d3hy+@xc zy;L2$E6#iU)~83$Z=13!`sSwFFtM3yAD!Fl z$}_*dTW@7}ep2~sUIvB_nosMV-nBiQc=Xp+{26WqQ)vh2JmO`4y`Sy<{B>Qnj{WTY z=LB!h)!lwJ@V9w%*=%L5dpC=2pV{HN_jthEi*Ijyk`g>xZQ)ne2O8gltW#Pp`(d)L%dEV=N_0+T0&d4Rj#qC`-pMP0>oO*SfVo}+qt-0@^ji)7KYjw1oQWXbx4vG+^6 zNqf^~uLf-;c)Du;9{Ips^R;i9UHSPj&L(n-(XC|vo4>=C9@z9PT*13fXW5aOwLi9( z**^N;Kjmvr%4a49hVQ}e7;pV7?{B@e5oKnv)9!usos72zT73F|^3v}U zzes5&zQ6k|XJ&5Sjr})^rWZ}WRJ-lYQXNpkDEGbHtzWykzHf*x%R^5Xpl~fo`+fSi z;L_haC*?ZNm4AQKdk)Wu|C;K(wOe&x|L@0Ngh;wRSiAJe(w-?xroQ`ra@x(b zw%lJOAOBun&wpRbdOb=hb|z%k``GE)|8~{ngVI2~8zK$dUo=JW)+CnWnn$P1KlgQN z%BGk1QeWEUURVA1f0u>u$u46Ch6B5P@Y#O4Yd@*>{kdh;(|^d_?@CR-d`V{QlO$dCSaiet-MzYt^P@Il(uNy)o?rSi2IB zW9GP4_ns&!5!|_Ld&T~(bM8dHxCgp|XYsfEZEwt~>pK-vPUn7pnR_d!%>K2-{Y`p; zN2_nBSzCU6!v#i!ja^)Rn*K>NG85j;s+LbV`)_k38VA1_3 zlk?Ydb0eQtm;ZbhyRA?y`rA?aJZEf$i~6~vo?EVW>ZDJdn5|rY)u!l()n099`M|1k z({4Xo@%Q&Taf!aFxj8p;?RNUUzjuAHE1H-ni_cNYa7pVM}i&EcreA~8~?Y$}Y^Ebb|HfgQD zZrtO(`-_w3Zhm|A)8)Tz*u3|=)auh}jr-}We>8&J`+j)8-Kt@_UG?qa_{Ujq|8>9J zU0wOR^EN1fO<$3};x=@dvh|D0(NFr;h#u-cbHZxs5t~Rx`+vH1+rsAMKVND5%bQ{1 zCcT@#j-0=CO}*~gxnR)U3z?HKs(^;&Qo5qDp+2Wh{Z@<))AT)h-s@e2856^Uy+Jzt zQ{LA-{hjdBZ$@W*{(kp;-a6||g9O%3&YXTV&wa_gd+MvdJ5RfzzW+`08|=Mxkc%6F zxAOPC#&Ghv6Y(?d&wlgub&lq}{N77{YazuDYfX314^7nt9DQ6hZjYXqGB8Mp-#>in`^rzY+LLPeqko+f{Xfrl|FnwUYnNWHOU;~ns_dxI z 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 U>+)FxndcxOPgg&ebxsLQ050Hw5C8xG 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