diff --git a/.dockerignore b/.dockerignore index 2e001ed43dd844adbf8853219d73d5cb196cbc42..96e722427c2b0250783623668ef89e3d6ac7083d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,13 +5,12 @@ coverage/ data/ deploy/ -manifests/ -hackfest/ -scripts/ -tmp/ - ecoc22/ +hackfest/ +manifests/ nfvsdn22/ oeccpsc22/ ofc22/ ofc23/ +scripts/ +tmp/ diff --git a/hackfest/commands.txt b/hackfest/commands.txt index 1b5d03dd4f8b64521401f1ab81bc5c2066eb68c4..840b83a245f6adf823b8ec097b16437c7d052826 100644 --- a/hackfest/commands.txt +++ b/hackfest/commands.txt @@ -337,8 +337,8 @@ $ python3 connectionServiceWithNotif_client.py $ sudo bash -c "$(curl -sL https://get.containerlab.dev)" ## Deploy proposed two SR node scenario -$ cd tfs-ctrl/hackfest/gnmi -$ sudo containerlab deploy -t srlinux.clab.yml +$ cd ~/tfs-ctrl/hackfest/gnmi +$ sudo containerlab deploy --topo srlinux.clab.yml ## Access SR Bash $ docker exec -it clab-srlinux-srl1 bash diff --git a/hackfest/containerlab/.gitignore b/hackfest/containerlab/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..48cbf38799194b5fb3896f3a27f16bff11369fb2 --- /dev/null +++ b/hackfest/containerlab/.gitignore @@ -0,0 +1,2 @@ +clab-tfs-scenario +.tfs-scenario.clab.yml.bak diff --git a/hackfest/containerlab/commands.txt b/hackfest/containerlab/commands.txt new file mode 100644 index 0000000000000000000000000000000000000000..18c629c0af2fe176a34f9b08a16405731c185243 --- /dev/null +++ b/hackfest/containerlab/commands.txt @@ -0,0 +1,99 @@ +############ +# ContainerLab +############ + +Refs: +https://documentation.nokia.com/srlinux/22-6/SR_Linux_Book_Files/SysMgmt_Guide/data-models.html#openconfig-ov +https://documentation.nokia.com/srlinux/SR_Linux_HTML_R21-11/SysMgmt_Guide/gnmi-interface.html#ai9ersv4qe +https://github.com/openconfig/kne/blob/v0.1.9/examples/nokia/srlinux-services/srl-openconfig.cfg.json +https://containerlab.dev/manual/kinds/srl/#default-node-configuration +https://learn.srlinux.dev/tutorials/infrastructure/kne/srl-with-oc-services/ +https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md +https://gnmic.kmrd.dev/cmd/get/ + + +IMPORTANT: for Nokia SR Linux, use kind "srl" and type "ixr6" + +## Download and install the latest release +$ sudo bash -c "$(curl -sL https://get.containerlab.dev)" -- -v 0.42.0 + +## Deploy proposed two SR node scenario +$ cd ~/tfs-ctrl/hackfest/containerlab +$ sudo containerlab deploy --topo tfs-scenario.clab.yml + +## Access SR Bash +$ docker exec -it clab-tfs-scenario-srl1 bash + +## Access SR CLI +$ docker exec -it clab-tfs-scenario-srl1 sr_cli + +## Destroy scenario +$ sudo containerlab destroy --topo tfs-scenario.clab.yml + + +## Enable OpenConfig data models and set as default: +$ docker exec -it clab-tfs-scenario-srl1 sr_cli +# enter candidate +# system management openconfig admin-state enable +# system gnmi-server network-instance mgmt yang-models openconfig +# commit stay +# quit + + +# Configure containerlab clients +docker exec -it clab-tfs-scenario-client1 bash + ip address add 172.16.1.10/24 dev eth1 + ip route add 172.16.2.0/24 via 172.16.1.1 + + ping 172.16.2.1 or 172.16.2.10 + +docker exec -it clab-tfs-scenario-client2 bash + ip address add 172.16.2.10/24 dev eth1 + ip route add 172.16.1.0/24 via 172.16.2.1 + + ping 172.16.1.1 or 172.16.1.10 + + + + +## Install gNMIc +$ sudo bash -c "$(curl -sL https://get-gnmic.kmrd.dev)" + +## gNMI Capabilities request +$ gnmic -a clab-srlinux-srl1 -u admin -p NokiaSrl1! --skip-verify capabilities + +## gNMI Get request +$ gnmic -a clab-srlinux-srl1 -u admin -p NokiaSrl1! --skip-verify -e json_ietf get --path /system/name/host-name +$ gnmic -a clab-srlinux-srl1 -u admin -p NokiaSrl1! --skip-verify -e json_ietf get --path /interface[name=mgmt0] + +## gNMI Set request +$ gnmic -a clab-srlinux-srl1 -u admin -p NokiaSrl1! --skip-verify -e json_ietf set --update-path /system/name/host-name --update-value slr11 + +(we check the changed value) +$ gnmic -a clab-srlinux-srl1 -u admin -p NokiaSrl1! --skip-verify -e json_ietf get --path /system/name/host-name + +## Subscribe request +$ gnmic -a clab-srlinux-srl1 -u admin -p NokiaSrl1! --skip-verify -e json_ietf subscribe --path /interface[name=mgmt0]/statistics +(In another terminal, you can generate traffic) +$ssh admin@clab-srlinux-srl1 + + + + +# Check configurations done: +gnmic -a 172.100.100.101 -u admin -p NokiaSrl1! --skip-verify -e json_ietf get --path '/network-instances' > srl1-nis.json +gnmic -a 172.100.100.101 -u admin -p NokiaSrl1! --skip-verify -e json_ietf get --path '/interfaces' > srl1-ifs.json +gnmic -a 172.100.100.102 -u admin -p NokiaSrl1! --skip-verify -e json_ietf get --path '/network-instances' > srl2-nis.json +gnmic -a 172.100.100.102 -u admin -p NokiaSrl1! --skip-verify -e json_ietf get --path '/interfaces' > srl2-ifs.json + + +# Delete elements: +gnmic -a 172.100.100.101 -u admin -p NokiaSrl1! --skip-verify -e json_ietf set --delete '/network-instances/network-instance[name=b19229e8]' +gnmic -a 172.100.100.101 -u admin -p NokiaSrl1! --skip-verify -e json_ietf set --delete '/interfaces/interface[name=ethernet-1/1]/subinterfaces/subinterface[index=0]' +gnmic -a 172.100.100.101 -u admin -p NokiaSrl1! --skip-verify -e json_ietf set --delete '/interfaces/interface[name=ethernet-1/2]/subinterfaces/subinterface[index=0]' +gnmic -a 172.100.100.102 -u admin -p NokiaSrl1! --skip-verify -e json_ietf set --delete '/network-instances/network-instance[name=b19229e8]' +gnmic -a 172.100.100.102 -u admin -p NokiaSrl1! --skip-verify -e json_ietf set --delete '/interfaces/interface[name=ethernet-1/1]/subinterfaces/subinterface[index=0]' +gnmic -a 172.100.100.102 -u admin -p NokiaSrl1! --skip-verify -e json_ietf set --delete '/interfaces/interface[name=ethernet-1/2]/subinterfaces/subinterface[index=0]' + +# Run gNMI Driver in standalone mode (advanced) +PYTHONPATH=./src python -m src.device.tests.test_gnmi diff --git a/hackfest/containerlab/tfs-descriptors/.gitkeep b/hackfest/containerlab/tfs-descriptors/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/hackfest/containerlab/tfs-descriptors/dc-2-dc-l3-service.json b/hackfest/containerlab/tfs-descriptors/dc-2-dc-l3-service.json new file mode 100644 index 0000000000000000000000000000000000000000..cb9ef972e4c314694a1a8a78bb5e30509a2de61f --- /dev/null +++ b/hackfest/containerlab/tfs-descriptors/dc-2-dc-l3-service.json @@ -0,0 +1,37 @@ +{ + "services": [ + { + "service_id": { + "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "dc-2-dc-l3-svc"} + }, + "service_type": 1, + "service_status": {"service_status": 1}, + "service_endpoint_ids": [ + {"device_id":{"device_uuid":{"uuid":"DC1"}},"endpoint_uuid":{"uuid":"int"}}, + {"device_id":{"device_uuid":{"uuid":"DC2"}},"endpoint_uuid":{"uuid":"int"}} + ], + "service_constraints": [], + "service_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "/device[SRL1]/settings", "resource_value": { + "static_routes": [{"prefix": "172.16.2.0/24", "next_hop": "172.0.0.2"}] + }}}, + {"action": 1, "custom": {"resource_key": "/device[SRL1]/endpoint[ethernet-1/1]/settings", "resource_value": { + "ipv4_address": "172.0.0.1", "ipv4_prefix": 30, "sub_interface_index": 0 + }}}, + {"action": 1, "custom": {"resource_key": "/device[SRL1]/endpoint[ethernet-1/2]/settings", "resource_value": { + "ipv4_address": "172.16.1.1", "ipv4_prefix": 24, "sub_interface_index": 0 + }}}, + + {"action": 1, "custom": {"resource_key": "/device[SRL2]/settings", "resource_value": { + "static_routes": [{"prefix": "172.16.1.0/24", "next_hop": "172.0.0.1"}] + }}}, + {"action": 1, "custom": {"resource_key": "/device[SRL2]/endpoint[ethernet-1/1]/settings", "resource_value": { + "ipv4_address": "172.0.0.2", "ipv4_prefix": 30, "sub_interface_index": 0 + }}}, + {"action": 1, "custom": {"resource_key": "/device[SRL2]/endpoint[ethernet-1/2]/settings", "resource_value": { + "ipv4_address": "172.16.2.1", "ipv4_prefix": 24, "sub_interface_index": 0 + }}} + ]} + } + ] +} diff --git a/hackfest/containerlab/tfs-descriptors/topology.json b/hackfest/containerlab/tfs-descriptors/topology.json new file mode 100644 index 0000000000000000000000000000000000000000..e4a49981f99339d77538af814365703e463d3db5 --- /dev/null +++ b/hackfest/containerlab/tfs-descriptors/topology.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/hackfest/containerlab/tfs-scenario.clab.yml b/hackfest/containerlab/tfs-scenario.clab.yml new file mode 100644 index 0000000000000000000000000000000000000000..df197ebea70906874515240f18d2f06c4c307c88 --- /dev/null +++ b/hackfest/containerlab/tfs-scenario.clab.yml @@ -0,0 +1,59 @@ +# 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. + +# Example based on clos01 example (http://containerlab.dev/lab-examples/min-clos/) + +# TFS 2 Nokia SR Linux nodes + 2 Linux clients +name: tfs-scenario + +mgmt: + network: mgmt-net + ipv4-subnet: 172.100.100.0/24 + +topology: + kinds: + srl: + image: ghcr.io/nokia/srlinux:23.3.1 + linux: + image: ghcr.io/hellt/network-multitool + nodes: + srl1: + kind: srl + type: ixr6 + cpu: 0.5 + memory: 1GB + mgmt-ipv4: 172.100.100.101 + #startup-config: srl1.cli + srl2: + kind: srl + type: ixr6 + cpu: 0.5 + memory: 1GB + mgmt-ipv4: 172.100.100.102 + #startup-config: srl2.cli + client1: + kind: linux + cpu: 0.1 + memory: 100MB + mgmt-ipv4: 172.100.100.201 + client2: + kind: linux + cpu: 0.1 + memory: 100MB + mgmt-ipv4: 172.100.100.202 + + links: + - endpoints: ["srl1:e1-1", "srl2:e1-1"] + - endpoints: ["client1:eth1", "srl1:e1-2"] + - endpoints: ["client2:eth1", "srl2:e1-2"] diff --git a/hackfest/tfs-descriptors/context-topology.json b/hackfest/tfs-descriptors/hackfest1/context-topology.json similarity index 100% rename from hackfest/tfs-descriptors/context-topology.json rename to hackfest/tfs-descriptors/hackfest1/context-topology.json diff --git a/hackfest/tfs-descriptors/device-all.json b/hackfest/tfs-descriptors/hackfest1/device-all.json similarity index 100% rename from hackfest/tfs-descriptors/device-all.json rename to hackfest/tfs-descriptors/hackfest1/device-all.json diff --git a/hackfest/tfs-descriptors/device-netconf-openconfig.json b/hackfest/tfs-descriptors/hackfest1/device-netconf-openconfig.json similarity index 100% rename from hackfest/tfs-descriptors/device-netconf-openconfig.json rename to hackfest/tfs-descriptors/hackfest1/device-netconf-openconfig.json diff --git a/hackfest/tfs-descriptors/device-tapi-ols.json b/hackfest/tfs-descriptors/hackfest1/device-tapi-ols.json similarity index 100% rename from hackfest/tfs-descriptors/device-tapi-ols.json rename to hackfest/tfs-descriptors/hackfest1/device-tapi-ols.json diff --git a/hackfest/tfs-descriptors/links.json b/hackfest/tfs-descriptors/hackfest1/links.json similarity index 100% rename from hackfest/tfs-descriptors/links.json rename to hackfest/tfs-descriptors/hackfest1/links.json diff --git a/hackfest/tfs-descriptors/service-l3vpn.json b/hackfest/tfs-descriptors/hackfest1/service-l3vpn.json similarity index 100% rename from hackfest/tfs-descriptors/service-l3vpn.json rename to hackfest/tfs-descriptors/hackfest1/service-l3vpn.json diff --git a/hackfest/tfs-descriptors/hackfest2/emulated-topology.json b/hackfest/tfs-descriptors/hackfest2/emulated-topology.json new file mode 100644 index 0000000000000000000000000000000000000000..6885c7d9082bfcb17447252636ea4efb0f500283 --- /dev/null +++ b/hackfest/tfs-descriptors/hackfest2/emulated-topology.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/hackfest/tfs-descriptors/hackfest2/emulated-topology.png b/hackfest/tfs-descriptors/hackfest2/emulated-topology.png new file mode 100644 index 0000000000000000000000000000000000000000..e8684e825cd8a92d58c58c785b45203f7b0abee2 Binary files /dev/null and b/hackfest/tfs-descriptors/hackfest2/emulated-topology.png differ diff --git a/hackfest/tfs-descriptors/hackfest2/l3-service.json b/hackfest/tfs-descriptors/hackfest2/l3-service.json new file mode 100644 index 0000000000000000000000000000000000000000..540d3a7a6744551ba13e59cd9ec2c23258e0c762 --- /dev/null +++ b/hackfest/tfs-descriptors/hackfest2/l3-service.json @@ -0,0 +1,44 @@ +{ + "services": [ + { + "service_id": { + "context_id": {"context_uuid": {"uuid": "admin"}}, + "service_uuid": {"uuid": "l3-service"} + }, + "service_type": 1, + "service_status": {"service_status": 1}, + "service_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/2"}}, + {"device_id": {"device_uuid": {"uuid": "R4"}}, "endpoint_uuid": {"uuid": "1/2"}} + ], + "service_constraints": [ + {"custom": {"constraint_type": "bandwidth[gbps]", "constraint_value": "10.0"}}, + {"custom": {"constraint_type": "latency[ms]", "constraint_value": "15.2"}} + ], + "service_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "/settings", "resource_value": { + "address_families": ["IPV4"], + "bgp_as": 65000, + "bgp_route_target": "65000:333", + "mtu": 1512 + }}}, + {"action": 1, "custom": {"resource_key": "/device[R1]/endpoint[1/2]/settings", "resource_value": { + "address_ip": "3.3.2.1", + "address_prefix": 24, + "route_distinguisher": "65000:123", + "router_id": "10.10.10.1", + "sub_interface_index": 400, + "vlan_id": 400 + }}}, + {"action": 1, "custom": {"resource_key": "/device[R4]/endpoint[1/2]/settings", "resource_value": { + "address_ip": "3.3.1.1", + "address_prefix": 24, + "route_distinguisher": "65000:123", + "router_id": "20.20.20.1", + "sub_interface_index": 400, + "vlan_id": 500 + }}} + ]} + } + ] +} diff --git a/manifests/deviceservice.yaml b/manifests/deviceservice.yaml index 22c0f5f9d124b76d0e477dce35d14811204c1496..a99e3e5884745aa1971e5ad7f914901e38f75d47 100644 --- a/manifests/deviceservice.yaml +++ b/manifests/deviceservice.yaml @@ -40,6 +40,11 @@ spec: env: - name: LOG_LEVEL value: "INFO" + startupProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:2020"] + failureThreshold: 30 + periodSeconds: 10 readinessProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:2020"] diff --git a/manifests/monitoringservice.yaml b/manifests/monitoringservice.yaml index 4447a1427980be6554228087924bf8e4ca775758..dbcfa68a036e3323f42e423de14002ba85576171 100644 --- a/manifests/monitoringservice.yaml +++ b/manifests/monitoringservice.yaml @@ -48,11 +48,11 @@ spec: command: ["/bin/grpc_health_probe", "-addr=:7070"] resources: requests: - cpu: 50m - memory: 64Mi + cpu: 250m + memory: 256Mi limits: - cpu: 500m - memory: 512Mi + cpu: 1000m + memory: 1024Mi --- apiVersion: v1 kind: Service diff --git a/manifests/pathcompservice.yaml b/manifests/pathcompservice.yaml index 15dd311972bcafadcccb765f75d73aa9566c3683..c85922d961ecc7b99e8fa2476b5e61db7ed52a9d 100644 --- a/manifests/pathcompservice.yaml +++ b/manifests/pathcompservice.yaml @@ -36,7 +36,7 @@ spec: - containerPort: 9192 env: - name: LOG_LEVEL - value: "DEBUG" + value: "INFO" readinessProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:10020"] @@ -69,10 +69,10 @@ spec: # timeoutSeconds: 5 resources: requests: - cpu: 100m + cpu: 250m memory: 256Mi limits: - cpu: 700m + cpu: 1000m memory: 1024Mi --- apiVersion: v1 diff --git a/my_deploy.sh b/my_deploy.sh index e3ad5e71ae58f8546ecf7f24bf2a3a4c0d03cb37..7e8abb1282655dd300d48457f655ac932cbb6e68 100755 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -22,16 +22,16 @@ 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 compute webui load_generator" -# Uncoment to activate Monitoring +# Uncomment to activate Monitoring #export TFS_COMPONENTS="${TFS_COMPONENTS} monitoring" -# Uncoment to activate Automation and Policy Manager +# Uncomment to activate Automation and Policy Manager #export TFS_COMPONENTS="${TFS_COMPONENTS} automation policy" -# Uncoment to activate Optical CyberSecurity +# Uncomment to activate Optical CyberSecurity #export TFS_COMPONENTS="${TFS_COMPONENTS} dbscanserving opticalattackmitigator opticalattackdetector opticalattackmanager" -# Uncoment to activate L3 CyberSecurity +# Uncomment to activate L3 CyberSecurity #export TFS_COMPONENTS="${TFS_COMPONENTS} l3_attackmitigator l3_centralizedattackdetector" # Set the tag you want to use for your images. @@ -41,9 +41,12 @@ export TFS_IMAGE_TAG="dev" export TFS_K8S_NAMESPACE="tfs" # Set additional manifest files to be applied after the deployment -export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml manifests/servicemonitors.yaml" +export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml" -# Uncoment when deploying Optical CyberSecurity +# Uncomment to monitor performance of components +export TFS_EXTRA_MANIFESTS="${TFS_EXTRA_MANIFESTS} manifests/servicemonitors.yaml" + +# Uncomment when deploying Optical CyberSecurity #export TFS_EXTRA_MANIFESTS="${TFS_EXTRA_MANIFESTS} manifests/cachingservice.yaml" # Set the new Grafana admin password diff --git a/proto/context.proto b/proto/context.proto index 39abc081d4a652eda246f876c5184faffc7065d5..9f779d8db310a98ea05682e619b1357c27c76904 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -195,6 +195,7 @@ enum DeviceDriverEnum { DEVICEDRIVER_ONF_TR_352 = 5; DEVICEDRIVER_XR = 6; DEVICEDRIVER_IETF_L2VPN = 7; + DEVICEDRIVER_GNMI_OPENCONFIG = 8; } enum DeviceOperationalStatusEnum { diff --git a/src/common/type_checkers/Assertions.py b/src/common/type_checkers/Assertions.py index d5476a9534ca6e2d74ba16d3af71ed367bc5ab51..25eb42fabf9c670256e9079a060aa38deb3c0f3d 100644 --- a/src/common/type_checkers/Assertions.py +++ b/src/common/type_checkers/Assertions.py @@ -34,6 +34,7 @@ def validate_device_driver_enum(message): 'DEVICEDRIVER_ONF_TR_352', 'DEVICEDRIVER_XR', 'DEVICEDRIVER_IETF_L2VPN', + 'DEVICEDRIVER_GNMI_OPENCONFIG', ] def validate_device_operational_status_enum(message): diff --git a/src/context/service/database/models/enums/DeviceDriver.py b/src/context/service/database/models/enums/DeviceDriver.py index a612803e235de2c6d2d8c91052416a675a3a3085..09be94b1d7ec041f2d3f50f832f15017fb62e63c 100644 --- a/src/context/service/database/models/enums/DeviceDriver.py +++ b/src/context/service/database/models/enums/DeviceDriver.py @@ -25,6 +25,7 @@ class ORM_DeviceDriverEnum(enum.Enum): ONF_TR_352 = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352 XR = DeviceDriverEnum.DEVICEDRIVER_XR IETF_L2VPN = DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN + GNMI_OPENCONFIG = DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG grpc_to_enum__device_driver = functools.partial( grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) diff --git a/src/device/requirements.in b/src/device/requirements.in index 0c5f5856a6f06950f185d5f7318d059575795abd..825ef8e21e6c904497e1be7f701f2a6cd53e6b38 100644 --- a/src/device/requirements.in +++ b/src/device/requirements.in @@ -15,6 +15,7 @@ anytree==2.8.0 APScheduler==3.8.1 +cryptography==36.0.2 #fastcache==1.1.0 Jinja2==3.0.3 ncclient==0.6.13 diff --git a/src/device/service/__main__.py b/src/device/service/__main__.py index a07a2ab90d15d99bdabe6b3fb6b0e0c9c497cf3c..401711b4e366dad5347cf771651516fe5e125d55 100644 --- a/src/device/service/__main__.py +++ b/src/device/service/__main__.py @@ -58,13 +58,14 @@ def main(): driver_factory = DriverFactory(DRIVERS) driver_instance_cache = DriverInstanceCache(driver_factory) - # Initialize drivers with existing devices in context - preload_drivers(driver_instance_cache) - # Starting device service grpc_service = DeviceService(driver_instance_cache) grpc_service.start() + # Initialize drivers with existing devices in context + LOGGER.info('Pre-loading drivers...') + preload_drivers(driver_instance_cache) + # Wait for Ctrl+C or termination signal while not terminate.wait(timeout=1.0): pass diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index b3b485a471899dd96a4985fedb4bb6ede2432921..4ae7128b0258536dae5fbed2ae86457d4f9f969f 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -70,6 +70,7 @@ DRIVERS.append( # DeviceDriverEnum.DEVICEDRIVER_P4, # DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY, # DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352, + # DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG, # ], #} ])) @@ -94,6 +95,17 @@ if LOAD_ALL_DEVICE_DRIVERS: } ])) +if LOAD_ALL_DEVICE_DRIVERS: + from .gnmi_openconfig.GnmiOpenConfigDriver import GnmiOpenConfigDriver # pylint: disable=wrong-import-position + DRIVERS.append( + (GnmiOpenConfigDriver, [ + { + # Real Packet Router, specifying gNMI OpenConfig Driver => use GnmiOpenConfigDriver + FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.PACKET_ROUTER, + FilterFieldEnum.DRIVER : DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG, + } + ])) + if LOAD_ALL_DEVICE_DRIVERS: from .transport_api.TransportApiDriver import TransportApiDriver # pylint: disable=wrong-import-position DRIVERS.append( diff --git a/src/device/service/drivers/gnmi_openconfig/DeltaSampleCache.py b/src/device/service/drivers/gnmi_openconfig/DeltaSampleCache.py new file mode 100644 index 0000000000000000000000000000000000000000..5083082fe5694a95e95d95cd8ed72563d77dc098 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/DeltaSampleCache.py @@ -0,0 +1,35 @@ +# 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. + +import copy +from typing import Any, Dict, Tuple, Union + +class DeltaSampleCache: + def __init__(self) -> None: + self._previous_samples : Dict[str, Tuple[float, Union[int, float]]] = dict() + + def get_delta(self, path : str, current_timestamp : float, current_value : Any) -> None: + previous_sample = copy.deepcopy(self._previous_samples.get(path)) + self._previous_samples[path] = current_timestamp, current_value + + if not isinstance(current_value, (int, float)): return None + if previous_sample is None: return current_timestamp, 0 + previous_timestamp, previous_value = previous_sample + if not isinstance(previous_value, (int, float)): return None + + delta_value = max(0, current_value - previous_value) + delay = current_timestamp - previous_timestamp + delta_sample = current_timestamp, delta_value / delay + + return delta_sample diff --git a/src/device/service/drivers/gnmi_openconfig/GnmiOpenConfigDriver.py b/src/device/service/drivers/gnmi_openconfig/GnmiOpenConfigDriver.py new file mode 100644 index 0000000000000000000000000000000000000000..882c0de07440c3b89c4a82f522d08155329b5d7e --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/GnmiOpenConfigDriver.py @@ -0,0 +1,100 @@ +# 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. + +import logging, queue, threading +from typing import Any, Iterator, List, Optional, Tuple, Union +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.type_checkers.Checkers import chk_type +from device.service.driver_api._Driver import _Driver +from .GnmiSessionHandler import GnmiSessionHandler + +DRIVER_NAME = 'gnmi_openconfig' +METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) + +class GnmiOpenConfigDriver(_Driver): + def __init__(self, address : str, port : int, **settings) -> None: + super().__init__(DRIVER_NAME, address, port, **settings) + self.__logger = logging.getLogger('{:s}:[{:s}:{:s}]'.format(str(__name__), str(self.address), str(self.port))) + self.__lock = threading.Lock() + self.__started = threading.Event() + self.__terminate = threading.Event() + self.__handler = GnmiSessionHandler(self.address, self.port, settings, self.__logger) + self.__out_samples = self.__handler.out_samples + + def Connect(self) -> bool: + with self.__lock: + if self.__started.is_set(): return True + self.__handler.connect() + self.__started.set() + return True + + def Disconnect(self) -> bool: + with self.__lock: + # Trigger termination of loops and processes + self.__terminate.set() + # If not started, assume it is already disconnected + if not self.__started.is_set(): return True + self.__handler.disconnect() + return True + + @metered_subclass_method(METRICS_POOL) + def GetInitialConfig(self) -> List[Tuple[str, Any]]: + with self.__lock: + return [] + + @metered_subclass_method(METRICS_POOL) + def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]: + chk_type('resources', resource_keys, list) + with self.__lock: + return self.__handler.get(resource_keys) + + @metered_subclass_method(METRICS_POOL) + def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('resources', resources, list) + if len(resources) == 0: return [] + with self.__lock: + return self.__handler.set(resources) + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('resources', resources, list) + if len(resources) == 0: return [] + with self.__lock: + return self.__handler.delete(resources) + + @metered_subclass_method(METRICS_POOL) + def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + chk_type('subscriptions', subscriptions, list) + if len(subscriptions) == 0: return [] + with self.__lock: + return self.__handler.subscribe(subscriptions) + + @metered_subclass_method(METRICS_POOL) + def UnsubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + chk_type('subscriptions', subscriptions, list) + if len(subscriptions) == 0: return [] + with self.__lock: + return self.__handler.unsubscribe(subscriptions) + + def GetState(self, blocking=False, terminate : Optional[threading.Event] = None) -> Iterator[Tuple[str, Any]]: + while True: + if self.__terminate.is_set(): break + if terminate is not None and terminate.is_set(): break + try: + sample = self.__out_samples.get(block=blocking, timeout=0.1) + except queue.Empty: + if blocking: continue + return + if sample is None: continue + yield sample diff --git a/src/device/service/drivers/gnmi_openconfig/GnmiSessionHandler.py b/src/device/service/drivers/gnmi_openconfig/GnmiSessionHandler.py new file mode 100644 index 0000000000000000000000000000000000000000..04dae4f5fcc6427c735b528b0ab32ba1c967709a --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/GnmiSessionHandler.py @@ -0,0 +1,332 @@ +# 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. + +import copy, grpc, json, logging, queue, threading +from typing import Any, Dict, List, Optional, Tuple, Union +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.type_checkers.Checkers import chk_float, chk_length, chk_string, chk_type +from .gnmi.gnmi_pb2_grpc import gNMIStub +from .gnmi.gnmi_pb2 import Encoding, GetRequest, SetRequest, UpdateResult # pylint: disable=no-name-in-module +from .handlers import ALL_RESOURCE_KEYS, compose, get_path, parse +from .tools.Capabilities import get_supported_encodings +from .tools.Channel import get_grpc_channel +from .tools.Path import path_from_string, path_to_string #, compose_path +from .tools.Subscriptions import Subscriptions +from .tools.Value import decode_value #, value_exists +from .MonitoringThread import MonitoringThread + +class GnmiSessionHandler: + def __init__(self, address : str, port : int, settings : Dict, logger : logging.Logger) -> None: + self._address = address + self._port = port + self._settings = copy.deepcopy(settings) + self._logger = logger + self._lock = threading.Lock() + self._connected = threading.Event() + self._username = settings.get('username') + self._password = settings.get('password') + self._use_tls = settings.get('use_tls', False) + self._channel : Optional[grpc.Channel] = None + self._stub : Optional[gNMIStub] = None + self._monit_thread = None + self._supported_encodings = None + self._subscriptions = Subscriptions() + self._in_subscriptions = queue.Queue() + self._out_samples = queue.Queue() + + @property + def subscriptions(self): return self._subscriptions + + @property + def in_subscriptions(self): return self._in_subscriptions + + @property + def out_samples(self): return self._out_samples + + def connect(self): + with self._lock: + self._channel = get_grpc_channel(self._address, self._port, self._use_tls, self._logger) + self._stub = gNMIStub(self._channel) + self._supported_encodings = get_supported_encodings( + self._stub, self._username, self._password, timeout=120) + self._monit_thread = MonitoringThread( + self._stub, self._logger, self._settings, self._in_subscriptions, self._out_samples) + self._monit_thread.start() + self._connected.set() + + def disconnect(self): + if not self._connected.is_set(): return + with self._lock: + self._monit_thread.stop() + self._monit_thread.join() + self._channel.close() + self._connected.clear() + + def get(self, resource_keys : List[str]) -> List[Tuple[str, Union[Any, None, Exception]]]: + if len(resource_keys) == 0: resource_keys = ALL_RESOURCE_KEYS + chk_type('resources', resource_keys, list) + + parsing_results = [] + + get_request = GetRequest() + get_request.type = GetRequest.DataType.ALL + get_request.encoding = Encoding.JSON_IETF + #get_request.use_models.add() # kept empty: return for all models supported + for i,resource_key in enumerate(resource_keys): + str_resource_name = 'resource_key[#{:d}]'.format(i) + try: + chk_string(str_resource_name, resource_key, allow_empty=False) + self._logger.debug('[GnmiSessionHandler:get] resource_key = {:s}'.format(str(resource_key))) + str_path = get_path(resource_key) + self._logger.debug('[GnmiSessionHandler:get] str_path = {:s}'.format(str(str_path))) + get_request.path.append(path_from_string(str_path)) + except Exception as e: # pylint: disable=broad-except + MSG = 'Exception parsing {:s}: {:s}' + self._logger.exception(MSG.format(str_resource_name, str(resource_key))) + parsing_results.append((resource_key, e)) # if validation fails, store the exception + + if len(parsing_results) > 0: + return parsing_results + + metadata = [('username', self._username), ('password', self._password)] + timeout = None # GNMI_SUBSCRIPTION_TIMEOUT = int(sampling_duration) + get_reply = self._stub.Get(get_request, metadata=metadata, timeout=timeout) + #self._logger.info('get_reply={:s}'.format(grpc_message_to_json_string(get_reply))) + + results = [] + #results[str_filter] = [i, None, False] # (index, value, processed?) + + for notification in get_reply.notification: + #for delete_path in notification.delete: + # self._logger.info('delete_path={:s}'.format(grpc_message_to_json_string(delete_path))) + # str_path = path_to_string(delete_path) + # resource_key_tuple = results.get(str_path) + # if resource_key_tuple is None: + # # pylint: disable=broad-exception-raised + # MSG = 'Unexpected Delete Path({:s}); requested resource_keys({:s})' + # raise Exception(MSG.format(str(str_path), str(resource_keys))) + # resource_key_tuple[2] = True + + for update in notification.update: + #self._logger.info('update={:s}'.format(grpc_message_to_json_string(update))) + str_path = path_to_string(update.path) + #resource_key_tuple = results.get(str_path) + #if resource_key_tuple is None: + # # pylint: disable=broad-exception-raised + # MSG = 'Unexpected Update Path({:s}); requested resource_keys({:s})' + # raise Exception(MSG.format(str(str_path), str(resource_keys))) + try: + value = decode_value(update.val) + #resource_key_tuple[1] = value + #resource_key_tuple[2] = True + results.extend(parse(str_path, value)) + except Exception as e: # pylint: disable=broad-except + MSG = 'Exception processing notification {:s}' + self._logger.exception(MSG.format(grpc_message_to_json_string(notification))) + results.append((str_path, e)) # if validation fails, store the exception + + #_results = sorted(results.items(), key=lambda x: x[1][0]) + #results = list() + #for resource_key,resource_key_tuple in _results: + # _, value, processed = resource_key_tuple + # value = value if processed else Exception('Not Processed') + # results.append((resource_key, value)) + return results + + def set(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + #resource_keys = [key for key,_ in resources] + #current_values = self.get(resource_keys) + + #resource_tuples = { + # resource_key : [i, value, value_exists(value), None] + # for i,(resource_key,value) in enumerate(current_values) + #} + + #self._logger.info('---0') + #self._logger.info(str(resource_tuples)) + + set_request = SetRequest() + #for resource_key in resource_keys: + for resource_key, resource_value in resources: + self._logger.info('---1') + self._logger.info(str(resource_key)) + self._logger.info(str(resource_value)) + #resource_tuple = resource_tuples.get(resource_key) + #if resource_tuple is None: continue + #_, value, exists, operation_done = resource_tuple + if isinstance(resource_value, str): resource_value = json.loads(resource_value) + str_path, str_data = compose(resource_key, resource_value, delete=False) + self._logger.info('---3') + self._logger.info(str(str_path)) + self._logger.info(str(str_data)) + set_request_list = set_request.update #if exists else set_request.replace + set_request_entry = set_request_list.add() + set_request_entry.path.CopyFrom(path_from_string(str_path)) + set_request_entry.val.json_val = str_data.encode('UTF-8') + + self._logger.info('set_request={:s}'.format(grpc_message_to_json_string(set_request))) + metadata = [('username', self._username), ('password', self._password)] + timeout = None # GNMI_SUBSCRIPTION_TIMEOUT = int(sampling_duration) + set_reply = self._stub.Set(set_request, metadata=metadata, timeout=timeout) + self._logger.info('set_reply={:s}'.format(grpc_message_to_json_string(set_reply))) + + results = [] + for (resource_key, resource_value), update_result in zip(resources, set_reply.response): + operation = update_result.op + if operation == UpdateResult.UPDATE: + results.append((resource_key, True)) + else: + results.append((resource_key, Exception('Unexpected'))) + + #str_path = path_to_string(update_result.path) + #resource_tuple = resource_tuples.get(str_path) + #if resource_tuple is None: continue + #resource_tuple[3] = operation + + #resource_tuples = sorted(resource_tuples.items(), key=lambda x: x[1][0]) + #results = list() + #for resource_key,resource_tuple in resource_tuples: + # _, _, exists, operation_done = resource_tuple + # desired_operation = 'update' if exists else 'replace' + # + # if operation_done == UpdateResult.INVALID: + # value = Exception('Invalid') + # elif operation_done == UpdateResult.DELETE: + # value = Exception('Unexpected Delete') + # elif operation_done == UpdateResult.REPLACE: + # value = True if desired_operation == 'replace' else Exception('Failed') + # elif operation_done == UpdateResult.UPDATE: + # value = True if desired_operation == 'update' else Exception('Failed') + # else: + # value = Exception('Unexpected') + # results.append((resource_key, value)) + return results + + def delete(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + #resource_keys = [key for key,_ in resources] + #current_values = self.get(resource_keys) + + #resource_tuples = { + # resource_key : [i, value, value_exists(value), None] + # for i,(resource_key,value) in enumerate(current_values) + #} + + #self._logger.info('---0') + #self._logger.info(str(resource_tuples)) + + set_request = SetRequest() + #for resource_key in resource_keys: + for resource_key, resource_value in resources: + self._logger.info('---1') + self._logger.info(str(resource_key)) + self._logger.info(str(resource_value)) + #resource_tuple = resource_tuples.get(resource_key) + #if resource_tuple is None: continue + #_, value, exists, operation_done = resource_tuple + #if not exists: continue + if isinstance(resource_value, str): resource_value = json.loads(resource_value) + str_path, str_data = compose(resource_key, resource_value, delete=True) + self._logger.info('---3') + self._logger.info(str(str_path)) + self._logger.info(str(str_data)) + set_request_entry = set_request.delete.add() + set_request_entry.CopyFrom(path_from_string(str_path)) + + self._logger.info('set_request={:s}'.format(grpc_message_to_json_string(set_request))) + metadata = [('username', self._username), ('password', self._password)] + timeout = None # GNMI_SUBSCRIPTION_TIMEOUT = int(sampling_duration) + set_reply = self._stub.Set(set_request, metadata=metadata, timeout=timeout) + self._logger.info('set_reply={:s}'.format(grpc_message_to_json_string(set_reply))) + + results = [] + for (resource_key, resource_value), update_result in zip(resources, set_reply.response): + operation = update_result.op + if operation == UpdateResult.DELETE: + results.append((resource_key, True)) + else: + results.append((resource_key, Exception('Unexpected'))) + + #str_path = path_to_string(update_result.path) + #resource_tuple = resource_tuples.get(str_path) + #if resource_tuple is None: continue + #resource_tuple[3] = operation + + #resource_tuples = sorted(resource_tuples.items(), key=lambda x: x[1][0]) + #results = list() + #for resource_key,resource_tuple in resource_tuples: + # _, _, exists, operation_done = resource_tuple + # if operation_done == UpdateResult.INVALID: + # value = Exception('Invalid') + # elif operation_done == UpdateResult.DELETE: + # value = True + # elif operation_done == UpdateResult.REPLACE: + # value = Exception('Unexpected Replace') + # elif operation_done == UpdateResult.UPDATE: + # value = Exception('Unexpected Update') + # else: + # value = Exception('Unexpected') + # results.append((resource_key, value)) + return results + + def subscribe(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + results = [] + for i,subscription in enumerate(subscriptions): + str_subscription_name = 'subscriptions[#{:d}]'.format(i) + try: + chk_type(str_subscription_name, subscription, (list, tuple)) + chk_length(str_subscription_name, subscription, min_length=3, max_length=3) + resource_key, sampling_duration, sampling_interval = subscription + chk_string(str_subscription_name + '.resource_key', resource_key, allow_empty=False) + chk_float(str_subscription_name + '.sampling_duration', sampling_duration, min_value=0) + chk_float(str_subscription_name + '.sampling_interval', sampling_interval, min_value=0) + except Exception as e: # pylint: disable=broad-except + MSG = 'Exception validating {:s}: {:s}' + self._logger.exception(MSG.format(str_subscription_name, str(resource_key))) + results.append(e) # if validation fails, store the exception + continue + + #resource_path = resource_key.split('/') + #self._subscriptions.add(resource_path, sampling_duration, sampling_interval, reference) + subscription = 'subscribe', resource_key, sampling_duration, sampling_interval + self._in_subscriptions.put_nowait(subscription) + results.append(True) + return results + + def unsubscribe(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + results = [] + for i,subscription in enumerate(subscriptions): + str_subscription_name = 'subscriptions[#{:d}]'.format(i) + try: + chk_type(str_subscription_name, subscription, (list, tuple)) + chk_length(str_subscription_name, subscription, min_length=3, max_length=3) + resource_key, sampling_duration, sampling_interval = subscription + chk_string(str_subscription_name + '.resource_key', resource_key, allow_empty=False) + chk_float(str_subscription_name + '.sampling_duration', sampling_duration, min_value=0) + chk_float(str_subscription_name + '.sampling_interval', sampling_interval, min_value=0) + except Exception as e: # pylint: disable=broad-except + MSG = 'Exception validating {:s}: {:s}' + self._logger.exception(MSG.format(str_subscription_name, str(resource_key))) + results.append(e) # if validation fails, store the exception + continue + + #resource_path = resource_key.split('/') + #reference = self._subscriptions.get(resource_path, sampling_duration, sampling_interval) + #if reference is None: + # results.append(False) + # continue + #self._subscriptions.delete(reference) + subscription = 'unsubscribe', resource_key, sampling_duration, sampling_interval + self._in_subscriptions.put_nowait(subscription) + results.append(True) + return results diff --git a/src/device/service/drivers/gnmi_openconfig/MonitoringThread.py b/src/device/service/drivers/gnmi_openconfig/MonitoringThread.py new file mode 100644 index 0000000000000000000000000000000000000000..7cbd0da87d15b6fac0ea7f4a5de3c02259a07dc8 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/MonitoringThread.py @@ -0,0 +1,171 @@ +# 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. + +# Ref: https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md +# Ref: https://github.com/openconfig/gnmi/blob/master/proto/gnmi/gnmi.proto + +from __future__ import annotations +import grpc, logging, queue, re, threading +from collections.abc import Iterator +from datetime import datetime +from typing import Dict +from common.tools.grpc.Tools import grpc_message_to_json_string +from .gnmi.gnmi_pb2 import ( # pylint: disable=no-name-in-module + QOSMarking, SubscribeRequest, Subscription, SubscriptionList, SubscriptionMode +) +from .gnmi.gnmi_pb2_grpc import gNMIStub +from .tools.Path import path_from_string, path_to_string +from .DeltaSampleCache import DeltaSampleCache + + +LOGGER = logging.getLogger(__name__) + +# SubscriptionList Mode: Mode of the subscription. +# STREAM = 0: Values streamed by the target. gNMI Specification Section 3.5.1.5.2 +# ONCE = 1: Values sent once-off by the target. gNMI Specification Section 3.5.1.5.1 +# POLL = 2: Values sent in response to a poll request. gNMI Specification Section 3.5.1.5.3 +GNMI_SUBSCRIPTION_LIST_MODE = SubscriptionList.Mode.STREAM + +# Path Prefix: Prefix used for paths. +GNMI_PATH_PREFIX = None + +# QOS MArking: DSCP marking to be used. +GNMI_QOS_MARKING = None + +# Allow Aggregation: Whether elements of the schema that are marked as eligible for aggregation +# should be aggregated or not. +GNMI_ALLOW_AGGREGATION = False + +# Encoding: The encoding that the target should use within the Notifications generated +# corresponding to the SubscriptionList. +GNMI_ENCODING = 'JSON' + +#Subscription Mode: The mode of the subscription, specifying how the target must return values +# in a subscription. gNMI Specification Section 3.5.1.3 +# TARGET_DEFINED = 0: The target selects the relevant mode for each element. +# ON_CHANGE = 1: The target sends an update on element value change. +# SAMPLE = 2: The target samples values according to the interval. +GNMI_SUBSCRIPTION_MODE = SubscriptionMode.SAMPLE + +# Suppress Redundant: Indicates whether values that have not changed should be sent in a SAMPLE +# subscription. gNMI Specification Section 3.5.1.3 +GNMI_SUPPRESS_REDUNDANT = False + +# Heartbeat Interval: Specifies the maximum allowable silent period in nanoseconds when +# suppress_redundant is in use. The target should send a value at least once in the period +# specified. gNMI Specification Section 3.5.1.3 +GNMI_HEARTBEAT_INTERVAL = 10 # seconds + +GNMI_SUBSCRIPTION_TIMEOUT = None + +class MonitoringThread(threading.Thread): + def __init__( + self, stub : gNMIStub, logger : logging.Logger, settings : Dict, + in_subscriptions : queue.Queue, out_samples : queue.Queue + ) -> None: + super().__init__(daemon=True) + self._terminate = threading.Event() + self._stub = stub + self._logger = logger + self._username = settings.get('username') + self._password = settings.get('password') + self._in_subscriptions = in_subscriptions + self._out_samples = out_samples + self._response_iterator = None + self._delta_sample_cache = DeltaSampleCache() + + def stop(self) -> None: + self._terminate.set() + if self._response_iterator is not None: + self._response_iterator.cancel() + + def generate_requests(self) -> Iterator[SubscribeRequest]: + subscriptions = [] + while not self._terminate.is_set(): + try: + subscription = self._in_subscriptions.get(block=True, timeout=0.1) + operation, resource_key, sampling_duration, sampling_interval = subscription # pylint: disable=unused-variable + if operation != 'subscribe': continue # Unsubscribe not supported by gNM, needs to cancel entire connection + # options.timeout = int(sampling_duration) + #_path = parse_xpath(resource_key) + path = path_from_string(resource_key) + subscription = Subscription( + path=path, mode=GNMI_SUBSCRIPTION_MODE, suppress_redundant=GNMI_SUPPRESS_REDUNDANT, + sample_interval=int(sampling_interval * 1000000000), + heartbeat_interval=int(GNMI_HEARTBEAT_INTERVAL * 1000000000)) + subscriptions.append(subscription) + except queue.Empty: + if len(subscriptions) == 0: continue + #self._logger.warning('[generate_requests] process') + prefix = path_from_string(GNMI_PATH_PREFIX) if GNMI_PATH_PREFIX is not None else None + qos = QOSMarking(marking=GNMI_QOS_MARKING) if GNMI_QOS_MARKING is not None else None + subscriptions_list = SubscriptionList( + prefix=prefix, mode=GNMI_SUBSCRIPTION_LIST_MODE, allow_aggregation=GNMI_ALLOW_AGGREGATION, + encoding=GNMI_ENCODING, subscription=subscriptions, qos=qos) + subscribe_request = SubscribeRequest(subscribe=subscriptions_list) + #str_subscribe_request = grpc_message_to_json_string(subscribe_request) + #self._logger.warning('[generate_requests] subscribe_request={:s}'.format(str_subscribe_request)) + yield subscribe_request + subscriptions = [] + except: # pylint: disable=bare-except + self._logger.exception('[generate_requests] Unhandled Exception') + + def run(self) -> None: + # Add a dummy subscription to be used as keep-alive + # usable only with SRLinux native data models + #subscription = ('/system/name/host-name', None, 1) + #self._in_subscriptions.put_nowait(subscription) + + try: + request_iterator = self.generate_requests() + metadata = [('username', self._username), ('password', self._password)] + timeout = None # GNMI_SUBSCRIPTION_TIMEOUT = int(sampling_duration) + self._response_iterator = self._stub.Subscribe(request_iterator, metadata=metadata, timeout=timeout) + for subscribe_response in self._response_iterator: + str_subscribe_response = grpc_message_to_json_string(subscribe_response) + self._logger.warning('[run] subscribe_response={:s}'.format(str_subscribe_response)) + update = subscribe_response.update + timestamp_device = float(update.timestamp) / 1.e9 + timestamp_local = datetime.timestamp(datetime.utcnow()) + # if difference between timestamp from device and local is lower than 1 second + if abs(timestamp_device - timestamp_local) <= 1: + # assume clocks are synchronized, use timestamp from device + timestamp = timestamp_device + else: + # might be clocks are not synchronized, use local timestamp + timestamp = timestamp_local + for update_entry in update.update: + str_path = path_to_string(update_entry.path) + #if str_path != '/system/name/host-name': continue + #counter_name = update_entry.path[-1].name + value_type = update_entry.val.WhichOneof('value') + value = getattr(update_entry.val, value_type) + if re.match(r'^[0-9]+$', value) is not None: + value = int(value) + elif re.match(r'^[0-9]*\.[0-9]*$', value) is not None: + value = float(value) + else: + value = str(value) + delta_sample = self._delta_sample_cache.get_delta(str_path, timestamp, value) + if delta_sample is None: + sample = (timestamp, str_path, value) + else: + sample = (delta_sample[0], str_path, delta_sample[1]) + self._logger.warning('[run] sample={:s}'.format(str(sample))) + self._out_samples.put_nowait(sample) + except grpc.RpcError as e: + if e.code() != grpc.StatusCode.CANCELLED: raise # pylint: disable=no-member + if e.details() != 'Locally cancelled by application!': raise # pylint: disable=no-member + except: # pylint: disable=bare-except + self._logger.exception('Unhandled Exception') diff --git a/src/device/service/drivers/gnmi_openconfig/__init__.py b/src/device/service/drivers/gnmi_openconfig/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/__init__.py @@ -0,0 +1,14 @@ +# 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. + diff --git a/src/device/service/drivers/gnmi_openconfig/gnmi/Acknowledgement.txt b/src/device/service/drivers/gnmi_openconfig/gnmi/Acknowledgement.txt new file mode 100644 index 0000000000000000000000000000000000000000..a004e1f58bdea3ba78263a06e6e473f593e1b390 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/gnmi/Acknowledgement.txt @@ -0,0 +1,25 @@ +This code is partially based on: +https://github.com/nokia/pygnmi/blob/master/gNMI_Subscribe.py + + +MIT License + +Copyright (c) 2017 Nokia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/device/service/drivers/gnmi_openconfig/gnmi/__init__.py b/src/device/service/drivers/gnmi_openconfig/gnmi/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/gnmi/__init__.py @@ -0,0 +1,14 @@ +# 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. + diff --git a/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi.proto b/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi.proto new file mode 100644 index 0000000000000000000000000000000000000000..292880a55586f5a5c05b0edcfc0f2f73baf7be13 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi.proto @@ -0,0 +1,501 @@ +syntax = "proto3"; + +import "google/protobuf/any.proto"; +import "google/protobuf/descriptor.proto"; +// import gnmi_ext.proto; + +// Package gNMI defines a service specification for the gRPC Network Management +// Interface. This interface is defined to be a standard interface via which +// a network management system ("client") can subscribe to state values, +// retrieve snapshots of state information, and manipulate the state of a data +// tree supported by a device ("target"). +// +// This document references the gNMI Specification which can be found at +// http://github.com/openconfig/reference/blob/master/rpc/gnmi +package gnmi; + +// Define a protobuf FileOption that defines the gNMI service version. +extend google.protobuf.FileOptions { + // The gNMI service semantic version. + string gnmi_service = 1001; +} + +// gNMI_service is the current version of the gNMI service, returned through +// the Capabilities RPC. +option (gnmi_service) = "0.8.0"; + +option go_package = "github.com/openconfig/gnmi/proto/gnmi"; +option java_multiple_files = true; +option java_outer_classname = "GnmiProto"; +option java_package = "com.github.gnmi.proto"; + + +service gNMI { + // Capabilities allows the client to retrieve the set of capabilities that + // is supported by the target. This allows the target to validate the + // service version that is implemented and retrieve the set of models that + // the target supports. The models can then be specified in subsequent RPCs + // to restrict the set of data that is utilized. + // Reference: gNMI Specification Section 3.2 + rpc Capabilities(CapabilityRequest) returns (CapabilityResponse); + // Retrieve a snapshot of data from the target. A Get RPC requests that the + // target snapshots a subset of the data tree as specified by the paths + // included in the message and serializes this to be returned to the + // client using the specified encoding. + // Reference: gNMI Specification Section 3.3 + rpc Get(GetRequest) returns (GetResponse); + // Set allows the client to modify the state of data on the target. The + // paths to modified along with the new values that the client wishes + // to set the value to. + // Reference: gNMI Specification Section 3.4 + rpc Set(SetRequest) returns (SetResponse); + // Subscribe allows a client to request the target to send it values + // of particular paths within the data tree. These values may be streamed + // at a particular cadence (STREAM), sent one off on a long-lived channel + // (POLL), or sent as a one-off retrieval (ONCE). + // Reference: gNMI Specification Section 3.5 + rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeResponse); +} +// The Extension message contains a single gNMI extension. +message Extension { + oneof ext { + RegisteredExtension registered_ext = 1; // A registered extension. + // Well known extensions. + MasterArbitration master_arbitration = 2; // Master arbitration extension. + History history = 3; // History extension. + } +} + +// The RegisteredExtension message defines an extension which is defined outside +// of this file. +message RegisteredExtension { + ExtensionID id = 1; // The unique ID assigned to this extension. + bytes msg = 2; // The binary-marshalled protobuf extension payload. +} + +// RegisteredExtension is an enumeration acting as a registry for extensions +// defined by external sources. +enum ExtensionID { + EID_UNSET = 0; + // New extensions are to be defined within this enumeration - their definition + // MUST link to a reference describing their implementation. + + // An experimental extension that may be used during prototyping of a new + // extension. + EID_EXPERIMENTAL = 999; +} + +// MasterArbitration is used to select the master among multiple gNMI clients +// with the same Roles. The client with the largest election_id is honored as +// the master. +// The document about gNMI master arbitration can be found at +// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-master-arbitration.md +message MasterArbitration { + Role role = 1; + Uint128 election_id = 2; +} + +// Representation of unsigned 128-bit integer. +message Uint128 { + uint64 high = 1; + uint64 low = 2; +} + +// There can be one master for each role. The role is identified by its id. +message Role { + string id = 1; + // More fields can be added if needed, for example, to specify what paths the + // role can read/write. +} + +// The History extension allows clients to request historical data. Its +// spec can be found at +// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-history.md +message History { + oneof request { + int64 snapshot_time = 1; // Nanoseconds since the epoch + TimeRange range = 2; + } +} + +message TimeRange { + int64 start = 1; // Nanoseconds since the epoch + int64 end = 2; // Nanoseconds since the epoch +} +// Notification is a re-usable message that is used to encode data from the +// target to the client. A Notification carries two types of changes to the data +// tree: +// - Deleted values (delete) - a set of paths that have been removed from the +// data tree. +// - Updated values (update) - a set of path-value pairs indicating the path +// whose value has changed in the data tree. +// Reference: gNMI Specification Section 2.1 +message Notification { + int64 timestamp = 1; // Timestamp in nanoseconds since Epoch. + Path prefix = 2; // Prefix used for paths in the message. + repeated Update update = 4; // Data elements that have changed values. + repeated Path delete = 5; // Data elements that have been deleted. + // This notification contains a set of paths that are always updated together + // referenced by a globally unique prefix. + bool atomic = 6; + // Reserved field numbers and identifiers. + reserved "alias"; + reserved 3; +} + +// Update is a re-usable message that is used to store a particular Path, +// Value pair. +// Reference: gNMI Specification Section 2.1 +message Update { + Path path = 1; // The path (key) for the update. + Value value = 2 [deprecated=true]; // The value (value) for the update. + TypedValue val = 3; // The explicitly typed update value. + uint32 duplicates = 4; // Number of coalesced duplicates. +} + +// TypedValue is used to encode a value being sent between the client and +// target (originated by either entity). +message TypedValue { + // One of the fields within the val oneof is populated with the value + // of the update. The type of the value being included in the Update + // determines which field should be populated. In the case that the + // encoding is a particular form of the base protobuf type, a specific + // field is used to store the value (e.g., json_val). + oneof value { + string string_val = 1; // String value. + int64 int_val = 2; // Integer value. + uint64 uint_val = 3; // Unsigned integer value. + bool bool_val = 4; // Bool value. + bytes bytes_val = 5; // Arbitrary byte sequence value. + float float_val = 6 [deprecated=true]; // Deprecated - use double_val. + double double_val = 14; // Floating point value. + Decimal64 decimal_val = 7 [deprecated=true]; // Deprecated - use double_val. + ScalarArray leaflist_val = 8; // Mixed type scalar array value. + google.protobuf.Any any_val = 9; // protobuf.Any encoded bytes. + bytes json_val = 10; // JSON-encoded text. + bytes json_ietf_val = 11; // JSON-encoded text per RFC7951. + string ascii_val = 12; // Arbitrary ASCII text. + // Protobuf binary encoded bytes. The message type is not included. + // See the specification at + // github.com/openconfig/reference/blob/master/rpc/gnmi/protobuf-vals.md + // for a complete specification. [Experimental] + bytes proto_bytes = 13; + } +} + +// Path encodes a data tree path as a series of repeated strings, with +// each element of the path representing a data tree node name and the +// associated attributes. +// Reference: gNMI Specification Section 2.2.2. +message Path { + // Elements of the path are no longer encoded as a string, but rather within + // the elem field as a PathElem message. + repeated string element = 1 [deprecated=true]; + string origin = 2; // Label to disambiguate path. + repeated PathElem elem = 3; // Elements of the path. + string target = 4; // The name of the target + // (Sec. 2.2.2.1) +} + +// PathElem encodes an element of a gNMI path, along with any attributes (keys) +// that may be associated with it. +// Reference: gNMI Specification Section 2.2.2. +message PathElem { + string name = 1; // The name of the element in the path. + map key = 2; // Map of key (attribute) name to value. +} + +// Value encodes a data tree node's value - along with the way in which +// the value is encoded. This message is deprecated by gNMI 0.3.0. +// Reference: gNMI Specification Section 2.2.3. +message Value { + option deprecated = true; + bytes value = 1; // Value of the variable being transmitted. + Encoding type = 2; // Encoding used for the value field. +} + +// Encoding defines the value encoding formats that are supported by the gNMI +// protocol. These encodings are used by both the client (when sending Set +// messages to modify the state of the target) and the target when serializing +// data to be returned to the client (in both Subscribe and Get RPCs). +// Reference: gNMI Specification Section 2.3 +enum Encoding { + JSON = 0; // JSON encoded text. + BYTES = 1; // Arbitrarily encoded bytes. + PROTO = 2; // Encoded according to scalar values of TypedValue. + ASCII = 3; // ASCII text of an out-of-band agreed format. + JSON_IETF = 4; // JSON encoded text as per RFC7951. +} + +// Error message previously utilised to return errors to the client. Deprecated +// in favour of using the google.golang.org/genproto/googleapis/rpc/status +// message in the RPC response. +// Reference: gNMI Specification Section 2.5 +message Error { + option deprecated = true; + uint32 code = 1; // Canonical gRPC error code. + string message = 2; // Human readable error. + google.protobuf.Any data = 3; // Optional additional information. +} + +// Decimal64 is used to encode a fixed precision decimal number. The value +// is expressed as a set of digits with the precision specifying the +// number of digits following the decimal point in the digit set. +// This message is deprecated in favor of encoding all floating point types +// as double precision. +message Decimal64 { + option deprecated = true; + int64 digits = 1; // Set of digits. + uint32 precision = 2; // Number of digits following the decimal point. +} + +// ScalarArray is used to encode a mixed-type array of values. +message ScalarArray { + // The set of elements within the array. Each TypedValue message should + // specify only elements that have a field identifier of 1-7 (i.e., the + // values are scalar values). + repeated TypedValue element = 1; +} + +// SubscribeRequest is the message sent by the client to the target when +// initiating a subscription to a set of paths within the data tree. The +// request field must be populated and the initial message must specify a +// SubscriptionList to initiate a subscription. +// Reference: gNMI Specification Section 3.5.1.1 +message SubscribeRequest { + oneof request { + SubscriptionList subscribe = 1; // Specify the paths within a subscription. + Poll poll = 3; // Trigger a polled update. + } + // Extension messages associated with the SubscribeRequest. See the + // gNMI extension specification for further definition. + repeated Extension extension = 5; + // Reserved field numbers and identifiers. + reserved 4; + reserved "aliases"; +} + +// Poll is sent within a SubscribeRequest to trigger the device to +// send telemetry updates for the paths that are associated with the +// subscription. +// Reference: gNMI Specification Section Section 3.5.1.4 +message Poll { +} + +// SubscribeResponse is the message used by the target within a Subscribe RPC. +// The target includes a Notification message which is used to transmit values +// of the path(s) that are associated with the subscription. The same message +// is to indicate that the target has sent all data values once (is +// synchronized). +// Reference: gNMI Specification Section 3.5.1.4 +message SubscribeResponse { + oneof response { + Notification update = 1; // Changed or sampled value for a path. + // Indicate target has sent all values associated with the subscription + // at least once. + bool sync_response = 3; + // Deprecated in favour of google.golang.org/genproto/googleapis/rpc/status + Error error = 4 [deprecated=true]; + } + // Extension messages associated with the SubscribeResponse. See the + // gNMI extension specification for further definition. + repeated Extension extension = 5; +} + +// SubscriptionList is used within a Subscribe message to specify the list of +// paths that the client wishes to subscribe to. The message consists of a +// list of (possibly prefixed) paths, and options that relate to the +// subscription. +// Reference: gNMI Specification Section 3.5.1.2 +message SubscriptionList { + Path prefix = 1; // Prefix used for paths. + repeated Subscription subscription = 2; // Set of subscriptions to create. + QOSMarking qos = 4; // DSCP marking to be used. + // Mode of the subscription. + enum Mode { + STREAM = 0; // Values streamed by the target (Sec. 3.5.1.5.2). + ONCE = 1; // Values sent once-off by the target (Sec. 3.5.1.5.1). + POLL = 2; // Values sent in response to a poll request (Sec. 3.5.1.5.3). + } + Mode mode = 5; + // Whether elements of the schema that are marked as eligible for aggregation + // should be aggregated or not. + bool allow_aggregation = 6; + // The set of schemas that define the elements of the data tree that should + // be sent by the target. + repeated ModelData use_models = 7; + // The encoding that the target should use within the Notifications generated + // corresponding to the SubscriptionList. + Encoding encoding = 8; + // An optional field to specify that only updates to current state should be + // sent to a client. If set, the initial state is not sent to the client but + // rather only the sync message followed by any subsequent updates to the + // current state. For ONCE and POLL modes, this causes the server to send only + // the sync message (Sec. 3.5.2.3). + bool updates_only = 9; + // Reserved field numbers and identifiers. + reserved 3; + reserved "use_aliases"; +} + +// Subscription is a single request within a SubscriptionList. The path +// specified is interpreted (along with the prefix) as the elements of the data +// tree that the client is subscribing to. The mode determines how the target +// should trigger updates to be sent. +// Reference: gNMI Specification Section 3.5.1.3 +message Subscription { + Path path = 1; // The data tree path. + SubscriptionMode mode = 2; // Subscription mode to be used. + uint64 sample_interval = 3; // ns between samples in SAMPLE mode. + // Indicates whether values that have not changed should be sent in a SAMPLE + // subscription. + bool suppress_redundant = 4; + // Specifies the maximum allowable silent period in nanoseconds when + // suppress_redundant is in use. The target should send a value at least once + // in the period specified. + uint64 heartbeat_interval = 5; +} + +// SubscriptionMode is the mode of the subscription, specifying how the +// target must return values in a subscription. +// Reference: gNMI Specification Section 3.5.1.3 +enum SubscriptionMode { + TARGET_DEFINED = 0; // The target selects the relevant mode for each element. + ON_CHANGE = 1; // The target sends an update on element value change. + SAMPLE = 2; // The target samples values according to the interval. +} + +// QOSMarking specifies the DSCP value to be set on transmitted telemetry +// updates from the target. +// Reference: gNMI Specification Section 3.5.1.2 +message QOSMarking { + uint32 marking = 1; +} + +// SetRequest is sent from a client to the target to update values in the data +// tree. Paths are either deleted by the client, or modified by means of being +// updated, or replaced. Where a replace is used, unspecified values are +// considered to be replaced, whereas when update is used the changes are +// considered to be incremental. The set of changes that are specified within +// a single SetRequest are considered to be a transaction. +// Reference: gNMI Specification Section 3.4.1 +message SetRequest { + Path prefix = 1; // Prefix used for paths in the message. + repeated Path delete = 2; // Paths to be deleted from the data tree. + repeated Update replace = 3; // Updates specifying elements to be replaced. + repeated Update update = 4; // Updates specifying elements to updated. + // Extension messages associated with the SetRequest. See the + // gNMI extension specification for further definition. + repeated Extension extension = 5; +} + +// SetResponse is the response to a SetRequest, sent from the target to the +// client. It reports the result of the modifications to the data tree that were +// specified by the client. Errors for this RPC should be reported using the +// https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto +// message in the RPC return. The gnmi.Error message can be used to add additional +// details where required. +// Reference: gNMI Specification Section 3.4.2 +message SetResponse { + Path prefix = 1; // Prefix used for paths. + // A set of responses specifying the result of the operations specified in + // the SetRequest. + repeated UpdateResult response = 2; + Error message = 3 [deprecated=true]; // The overall status of the transaction. + int64 timestamp = 4; // Timestamp of transaction (ns since epoch). + // Extension messages associated with the SetResponse. See the + // gNMI extension specification for further definition. + repeated Extension extension = 5; +} + +// UpdateResult is used within the SetResponse message to communicate the +// result of an operation specified within a SetRequest message. +// Reference: gNMI Specification Section 3.4.2 +message UpdateResult { + // The operation that was associated with the Path specified. + enum Operation { + INVALID = 0; + DELETE = 1; // The result relates to a delete of Path. + REPLACE = 2; // The result relates to a replace of Path. + UPDATE = 3; // The result relates to an update of Path. + } + // Deprecated timestamp for the UpdateResult, this field has been + // replaced by the timestamp within the SetResponse message, since + // all mutations effected by a set should be applied as a single + // transaction. + int64 timestamp = 1 [deprecated=true]; + Path path = 2; // Path associated with the update. + Error message = 3 [deprecated=true]; // Status of the update operation. + Operation op = 4; // Update operation type. +} + +// GetRequest is sent when a client initiates a Get RPC. It is used to specify +// the set of data elements for which the target should return a snapshot of +// data. The use_models field specifies the set of schema modules that are to +// be used by the target - where use_models is not specified then the target +// must use all schema models that it has. +// Reference: gNMI Specification Section 3.3.1 +message GetRequest { + Path prefix = 1; // Prefix used for paths. + repeated Path path = 2; // Paths requested by the client. + // Type of elements within the data tree. + enum DataType { + ALL = 0; // All data elements. + CONFIG = 1; // Config (rw) only elements. + STATE = 2; // State (ro) only elements. + // Data elements marked in the schema as operational. This refers to data + // elements whose value relates to the state of processes or interactions + // running on the device. + OPERATIONAL = 3; + } + DataType type = 3; // The type of data being requested. + Encoding encoding = 5; // Encoding to be used. + repeated ModelData use_models = 6; // The schema models to be used. + // Extension messages associated with the GetRequest. See the + // gNMI extension specification for further definition. + repeated Extension extension = 7; +} + +// GetResponse is used by the target to respond to a GetRequest from a client. +// The set of Notifications corresponds to the data values that are requested +// by the client in the GetRequest. +// Reference: gNMI Specification Section 3.3.2 +message GetResponse { + repeated Notification notification = 1; // Data values. + Error error = 2 [deprecated=true]; // Errors that occurred in the Get. + // Extension messages associated with the GetResponse. See the + // gNMI extension specification for further definition. + repeated Extension extension = 3; +} + +// CapabilityRequest is sent by the client in the Capabilities RPC to request +// that the target reports its capabilities. +// Reference: gNMI Specification Section 3.2.1 +message CapabilityRequest { + // Extension messages associated with the CapabilityRequest. See the + // gNMI extension specification for further definition. + repeated Extension extension = 1; +} + +// CapabilityResponse is used by the target to report its capabilities to the +// client within the Capabilities RPC. +// Reference: gNMI Specification Section 3.2.2 +message CapabilityResponse { + repeated ModelData supported_models = 1; // Supported schema models. + repeated Encoding supported_encodings = 2; // Supported encodings. + string gNMI_version = 3; // Supported gNMI version. + // Extension messages associated with the CapabilityResponse. See the + // gNMI extension specification for further definition. + repeated Extension extension = 4; +} + +// ModelData is used to describe a set of schema modules. It can be used in a +// CapabilityResponse where a target reports the set of modules that it +// supports, and within the SubscribeRequest and GetRequest messages to specify +// the set of models from which data tree elements should be reported. +// Reference: gNMI Specification Section 3.2.3 +message ModelData { + string name = 1; // Name of the model. + string organization = 2; // Organization publishing the model. + string version = 3; // Semantic version of the model. +} \ No newline at end of file diff --git a/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_ext.proto b/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_ext.proto new file mode 100644 index 0000000000000000000000000000000000000000..9960f12afa9d19a45b3d30fd127cb27586f02607 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_ext.proto @@ -0,0 +1,76 @@ +syntax = "proto3"; + +// Package gnmi_ext defines a set of extensions messages which can be optionally +// included with the request and response messages of gNMI RPCs. A set of +// well-known extensions are defined within this file, along with a registry for +// extensions defined outside of this package. +package gnmi_ext; + +option go_package = "github.com/openconfig/gnmi/proto/gnmi_ext"; + +// The Extension message contains a single gNMI extension. +message Extension { + oneof ext { + RegisteredExtension registered_ext = 1; // A registered extension. + // Well known extensions. + MasterArbitration master_arbitration = 2; // Master arbitration extension. + History history = 3; // History extension. + } +} + +// The RegisteredExtension message defines an extension which is defined outside +// of this file. +message RegisteredExtension { + ExtensionID id = 1; // The unique ID assigned to this extension. + bytes msg = 2; // The binary-marshalled protobuf extension payload. +} + +// RegisteredExtension is an enumeration acting as a registry for extensions +// defined by external sources. +enum ExtensionID { + EID_UNSET = 0; + // New extensions are to be defined within this enumeration - their definition + // MUST link to a reference describing their implementation. + + // An experimental extension that may be used during prototyping of a new + // extension. + EID_EXPERIMENTAL = 999; +} + +// MasterArbitration is used to select the master among multiple gNMI clients +// with the same Roles. The client with the largest election_id is honored as +// the master. +// The document about gNMI master arbitration can be found at +// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-master-arbitration.md +message MasterArbitration { + Role role = 1; + Uint128 election_id = 2; +} + +// Representation of unsigned 128-bit integer. +message Uint128 { + uint64 high = 1; + uint64 low = 2; +} + +// There can be one master for each role. The role is identified by its id. +message Role { + string id = 1; + // More fields can be added if needed, for example, to specify what paths the + // role can read/write. +} + +// The History extension allows clients to request historical data. Its +// spec can be found at +// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-history.md +message History { + oneof request { + int64 snapshot_time = 1; // Nanoseconds since the epoch + TimeRange range = 2; + } +} + +message TimeRange { + int64 start = 1; // Nanoseconds since the epoch + int64 end = 2; // Nanoseconds since the epoch +} \ No newline at end of file diff --git a/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_pb2.py b/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_pb2.py new file mode 100644 index 0000000000000000000000000000000000000000..af67f9a8ca1d4e01d1a95a0d86adbabe6d8e83ea --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_pb2.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: gnmi.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 +from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ngnmi.proto\x12\x04gnmi\x1a\x19google/protobuf/any.proto\x1a google/protobuf/descriptor.proto\"\xa0\x01\n\tExtension\x12\x33\n\x0eregistered_ext\x18\x01 \x01(\x0b\x32\x19.gnmi.RegisteredExtensionH\x00\x12\x35\n\x12master_arbitration\x18\x02 \x01(\x0b\x32\x17.gnmi.MasterArbitrationH\x00\x12 \n\x07history\x18\x03 \x01(\x0b\x32\r.gnmi.HistoryH\x00\x42\x05\n\x03\x65xt\"A\n\x13RegisteredExtension\x12\x1d\n\x02id\x18\x01 \x01(\x0e\x32\x11.gnmi.ExtensionID\x12\x0b\n\x03msg\x18\x02 \x01(\x0c\"Q\n\x11MasterArbitration\x12\x18\n\x04role\x18\x01 \x01(\x0b\x32\n.gnmi.Role\x12\"\n\x0b\x65lection_id\x18\x02 \x01(\x0b\x32\r.gnmi.Uint128\"$\n\x07Uint128\x12\x0c\n\x04high\x18\x01 \x01(\x04\x12\x0b\n\x03low\x18\x02 \x01(\x04\"\x12\n\x04Role\x12\n\n\x02id\x18\x01 \x01(\t\"O\n\x07History\x12\x17\n\rsnapshot_time\x18\x01 \x01(\x03H\x00\x12 \n\x05range\x18\x02 \x01(\x0b\x32\x0f.gnmi.TimeRangeH\x00\x42\t\n\x07request\"\'\n\tTimeRange\x12\r\n\x05start\x18\x01 \x01(\x03\x12\x0b\n\x03\x65nd\x18\x02 \x01(\x03\"\x94\x01\n\x0cNotification\x12\x11\n\ttimestamp\x18\x01 \x01(\x03\x12\x1a\n\x06prefix\x18\x02 \x01(\x0b\x32\n.gnmi.Path\x12\x1c\n\x06update\x18\x04 \x03(\x0b\x32\x0c.gnmi.Update\x12\x1a\n\x06\x64\x65lete\x18\x05 \x03(\x0b\x32\n.gnmi.Path\x12\x0e\n\x06\x61tomic\x18\x06 \x01(\x08J\x04\x08\x03\x10\x04R\x05\x61lias\"u\n\x06Update\x12\x18\n\x04path\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0b.gnmi.ValueB\x02\x18\x01\x12\x1d\n\x03val\x18\x03 \x01(\x0b\x32\x10.gnmi.TypedValue\x12\x12\n\nduplicates\x18\x04 \x01(\r\"\x83\x03\n\nTypedValue\x12\x14\n\nstring_val\x18\x01 \x01(\tH\x00\x12\x11\n\x07int_val\x18\x02 \x01(\x03H\x00\x12\x12\n\x08uint_val\x18\x03 \x01(\x04H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x13\n\tbytes_val\x18\x05 \x01(\x0cH\x00\x12\x17\n\tfloat_val\x18\x06 \x01(\x02\x42\x02\x18\x01H\x00\x12\x14\n\ndouble_val\x18\x0e \x01(\x01H\x00\x12*\n\x0b\x64\x65\x63imal_val\x18\x07 \x01(\x0b\x32\x0f.gnmi.Decimal64B\x02\x18\x01H\x00\x12)\n\x0cleaflist_val\x18\x08 \x01(\x0b\x32\x11.gnmi.ScalarArrayH\x00\x12\'\n\x07\x61ny_val\x18\t \x01(\x0b\x32\x14.google.protobuf.AnyH\x00\x12\x12\n\x08json_val\x18\n \x01(\x0cH\x00\x12\x17\n\rjson_ietf_val\x18\x0b \x01(\x0cH\x00\x12\x13\n\tascii_val\x18\x0c \x01(\tH\x00\x12\x15\n\x0bproto_bytes\x18\r \x01(\x0cH\x00\x42\x07\n\x05value\"Y\n\x04Path\x12\x13\n\x07\x65lement\x18\x01 \x03(\tB\x02\x18\x01\x12\x0e\n\x06origin\x18\x02 \x01(\t\x12\x1c\n\x04\x65lem\x18\x03 \x03(\x0b\x32\x0e.gnmi.PathElem\x12\x0e\n\x06target\x18\x04 \x01(\t\"j\n\x08PathElem\x12\x0c\n\x04name\x18\x01 \x01(\t\x12$\n\x03key\x18\x02 \x03(\x0b\x32\x17.gnmi.PathElem.KeyEntry\x1a*\n\x08KeyEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"8\n\x05Value\x12\r\n\x05value\x18\x01 \x01(\x0c\x12\x1c\n\x04type\x18\x02 \x01(\x0e\x32\x0e.gnmi.Encoding:\x02\x18\x01\"N\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x01(\r\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\"\n\x04\x64\x61ta\x18\x03 \x01(\x0b\x32\x14.google.protobuf.Any:\x02\x18\x01\"2\n\tDecimal64\x12\x0e\n\x06\x64igits\x18\x01 \x01(\x03\x12\x11\n\tprecision\x18\x02 \x01(\r:\x02\x18\x01\"0\n\x0bScalarArray\x12!\n\x07\x65lement\x18\x01 \x03(\x0b\x32\x10.gnmi.TypedValue\"\x99\x01\n\x10SubscribeRequest\x12+\n\tsubscribe\x18\x01 \x01(\x0b\x32\x16.gnmi.SubscriptionListH\x00\x12\x1a\n\x04poll\x18\x03 \x01(\x0b\x32\n.gnmi.PollH\x00\x12\"\n\textension\x18\x05 \x03(\x0b\x32\x0f.gnmi.ExtensionB\t\n\x07requestJ\x04\x08\x04\x10\x05R\x07\x61liases\"\x06\n\x04Poll\"\xa4\x01\n\x11SubscribeResponse\x12$\n\x06update\x18\x01 \x01(\x0b\x32\x12.gnmi.NotificationH\x00\x12\x17\n\rsync_response\x18\x03 \x01(\x08H\x00\x12 \n\x05\x65rror\x18\x04 \x01(\x0b\x32\x0b.gnmi.ErrorB\x02\x18\x01H\x00\x12\"\n\textension\x18\x05 \x03(\x0b\x32\x0f.gnmi.ExtensionB\n\n\x08response\"\xd5\x02\n\x10SubscriptionList\x12\x1a\n\x06prefix\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12(\n\x0csubscription\x18\x02 \x03(\x0b\x32\x12.gnmi.Subscription\x12\x1d\n\x03qos\x18\x04 \x01(\x0b\x32\x10.gnmi.QOSMarking\x12)\n\x04mode\x18\x05 \x01(\x0e\x32\x1b.gnmi.SubscriptionList.Mode\x12\x19\n\x11\x61llow_aggregation\x18\x06 \x01(\x08\x12#\n\nuse_models\x18\x07 \x03(\x0b\x32\x0f.gnmi.ModelData\x12 \n\x08\x65ncoding\x18\x08 \x01(\x0e\x32\x0e.gnmi.Encoding\x12\x14\n\x0cupdates_only\x18\t \x01(\x08\"&\n\x04Mode\x12\n\n\x06STREAM\x10\x00\x12\x08\n\x04ONCE\x10\x01\x12\x08\n\x04POLL\x10\x02J\x04\x08\x03\x10\x04R\x0buse_aliases\"\x9f\x01\n\x0cSubscription\x12\x18\n\x04path\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12$\n\x04mode\x18\x02 \x01(\x0e\x32\x16.gnmi.SubscriptionMode\x12\x17\n\x0fsample_interval\x18\x03 \x01(\x04\x12\x1a\n\x12suppress_redundant\x18\x04 \x01(\x08\x12\x1a\n\x12heartbeat_interval\x18\x05 \x01(\x04\"\x1d\n\nQOSMarking\x12\x0f\n\x07marking\x18\x01 \x01(\r\"\xa5\x01\n\nSetRequest\x12\x1a\n\x06prefix\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12\x1a\n\x06\x64\x65lete\x18\x02 \x03(\x0b\x32\n.gnmi.Path\x12\x1d\n\x07replace\x18\x03 \x03(\x0b\x32\x0c.gnmi.Update\x12\x1c\n\x06update\x18\x04 \x03(\x0b\x32\x0c.gnmi.Update\x12\"\n\textension\x18\x05 \x03(\x0b\x32\x0f.gnmi.Extension\"\xa8\x01\n\x0bSetResponse\x12\x1a\n\x06prefix\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12$\n\x08response\x18\x02 \x03(\x0b\x32\x12.gnmi.UpdateResult\x12 \n\x07message\x18\x03 \x01(\x0b\x32\x0b.gnmi.ErrorB\x02\x18\x01\x12\x11\n\ttimestamp\x18\x04 \x01(\x03\x12\"\n\textension\x18\x05 \x03(\x0b\x32\x0f.gnmi.Extension\"\xca\x01\n\x0cUpdateResult\x12\x15\n\ttimestamp\x18\x01 \x01(\x03\x42\x02\x18\x01\x12\x18\n\x04path\x18\x02 \x01(\x0b\x32\n.gnmi.Path\x12 \n\x07message\x18\x03 \x01(\x0b\x32\x0b.gnmi.ErrorB\x02\x18\x01\x12(\n\x02op\x18\x04 \x01(\x0e\x32\x1c.gnmi.UpdateResult.Operation\"=\n\tOperation\x12\x0b\n\x07INVALID\x10\x00\x12\n\n\x06\x44\x45LETE\x10\x01\x12\x0b\n\x07REPLACE\x10\x02\x12\n\n\x06UPDATE\x10\x03\"\x93\x02\n\nGetRequest\x12\x1a\n\x06prefix\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12\x18\n\x04path\x18\x02 \x03(\x0b\x32\n.gnmi.Path\x12\'\n\x04type\x18\x03 \x01(\x0e\x32\x19.gnmi.GetRequest.DataType\x12 \n\x08\x65ncoding\x18\x05 \x01(\x0e\x32\x0e.gnmi.Encoding\x12#\n\nuse_models\x18\x06 \x03(\x0b\x32\x0f.gnmi.ModelData\x12\"\n\textension\x18\x07 \x03(\x0b\x32\x0f.gnmi.Extension\";\n\x08\x44\x61taType\x12\x07\n\x03\x41LL\x10\x00\x12\n\n\x06\x43ONFIG\x10\x01\x12\t\n\x05STATE\x10\x02\x12\x0f\n\x0bOPERATIONAL\x10\x03\"{\n\x0bGetResponse\x12(\n\x0cnotification\x18\x01 \x03(\x0b\x32\x12.gnmi.Notification\x12\x1e\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x0b.gnmi.ErrorB\x02\x18\x01\x12\"\n\textension\x18\x03 \x03(\x0b\x32\x0f.gnmi.Extension\"7\n\x11\x43\x61pabilityRequest\x12\"\n\textension\x18\x01 \x03(\x0b\x32\x0f.gnmi.Extension\"\xa6\x01\n\x12\x43\x61pabilityResponse\x12)\n\x10supported_models\x18\x01 \x03(\x0b\x32\x0f.gnmi.ModelData\x12+\n\x13supported_encodings\x18\x02 \x03(\x0e\x32\x0e.gnmi.Encoding\x12\x14\n\x0cgNMI_version\x18\x03 \x01(\t\x12\"\n\textension\x18\x04 \x03(\x0b\x32\x0f.gnmi.Extension\"@\n\tModelData\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0corganization\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t*3\n\x0b\x45xtensionID\x12\r\n\tEID_UNSET\x10\x00\x12\x15\n\x10\x45ID_EXPERIMENTAL\x10\xe7\x07*D\n\x08\x45ncoding\x12\x08\n\x04JSON\x10\x00\x12\t\n\x05\x42YTES\x10\x01\x12\t\n\x05PROTO\x10\x02\x12\t\n\x05\x41SCII\x10\x03\x12\r\n\tJSON_IETF\x10\x04*A\n\x10SubscriptionMode\x12\x12\n\x0eTARGET_DEFINED\x10\x00\x12\r\n\tON_CHANGE\x10\x01\x12\n\n\x06SAMPLE\x10\x02\x32\xe3\x01\n\x04gNMI\x12\x41\n\x0c\x43\x61pabilities\x12\x17.gnmi.CapabilityRequest\x1a\x18.gnmi.CapabilityResponse\x12*\n\x03Get\x12\x10.gnmi.GetRequest\x1a\x11.gnmi.GetResponse\x12*\n\x03Set\x12\x10.gnmi.SetRequest\x1a\x11.gnmi.SetResponse\x12@\n\tSubscribe\x12\x16.gnmi.SubscribeRequest\x1a\x17.gnmi.SubscribeResponse(\x01\x30\x01:3\n\x0cgnmi_service\x12\x1c.google.protobuf.FileOptions\x18\xe9\x07 \x01(\tBS\n\x15\x63om.github.gnmi.protoB\tGnmiProtoP\x01Z%github.com/openconfig/gnmi/proto/gnmi\xca>\x05\x30.8.0b\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gnmi_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + google_dot_protobuf_dot_descriptor__pb2.FileOptions.RegisterExtension(gnmi_service) + + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'\n\025com.github.gnmi.protoB\tGnmiProtoP\001Z%github.com/openconfig/gnmi/proto/gnmi\312>\0050.8.0' + _UPDATE.fields_by_name['value']._options = None + _UPDATE.fields_by_name['value']._serialized_options = b'\030\001' + _TYPEDVALUE.fields_by_name['float_val']._options = None + _TYPEDVALUE.fields_by_name['float_val']._serialized_options = b'\030\001' + _TYPEDVALUE.fields_by_name['decimal_val']._options = None + _TYPEDVALUE.fields_by_name['decimal_val']._serialized_options = b'\030\001' + _PATH.fields_by_name['element']._options = None + _PATH.fields_by_name['element']._serialized_options = b'\030\001' + _PATHELEM_KEYENTRY._options = None + _PATHELEM_KEYENTRY._serialized_options = b'8\001' + _VALUE._options = None + _VALUE._serialized_options = b'\030\001' + _ERROR._options = None + _ERROR._serialized_options = b'\030\001' + _DECIMAL64._options = None + _DECIMAL64._serialized_options = b'\030\001' + _SUBSCRIBERESPONSE.fields_by_name['error']._options = None + _SUBSCRIBERESPONSE.fields_by_name['error']._serialized_options = b'\030\001' + _SETRESPONSE.fields_by_name['message']._options = None + _SETRESPONSE.fields_by_name['message']._serialized_options = b'\030\001' + _UPDATERESULT.fields_by_name['timestamp']._options = None + _UPDATERESULT.fields_by_name['timestamp']._serialized_options = b'\030\001' + _UPDATERESULT.fields_by_name['message']._options = None + _UPDATERESULT.fields_by_name['message']._serialized_options = b'\030\001' + _GETRESPONSE.fields_by_name['error']._options = None + _GETRESPONSE.fields_by_name['error']._serialized_options = b'\030\001' + _EXTENSIONID._serialized_start=3780 + _EXTENSIONID._serialized_end=3831 + _ENCODING._serialized_start=3833 + _ENCODING._serialized_end=3901 + _SUBSCRIPTIONMODE._serialized_start=3903 + _SUBSCRIPTIONMODE._serialized_end=3968 + _EXTENSION._serialized_start=82 + _EXTENSION._serialized_end=242 + _REGISTEREDEXTENSION._serialized_start=244 + _REGISTEREDEXTENSION._serialized_end=309 + _MASTERARBITRATION._serialized_start=311 + _MASTERARBITRATION._serialized_end=392 + _UINT128._serialized_start=394 + _UINT128._serialized_end=430 + _ROLE._serialized_start=432 + _ROLE._serialized_end=450 + _HISTORY._serialized_start=452 + _HISTORY._serialized_end=531 + _TIMERANGE._serialized_start=533 + _TIMERANGE._serialized_end=572 + _NOTIFICATION._serialized_start=575 + _NOTIFICATION._serialized_end=723 + _UPDATE._serialized_start=725 + _UPDATE._serialized_end=842 + _TYPEDVALUE._serialized_start=845 + _TYPEDVALUE._serialized_end=1232 + _PATH._serialized_start=1234 + _PATH._serialized_end=1323 + _PATHELEM._serialized_start=1325 + _PATHELEM._serialized_end=1431 + _PATHELEM_KEYENTRY._serialized_start=1389 + _PATHELEM_KEYENTRY._serialized_end=1431 + _VALUE._serialized_start=1433 + _VALUE._serialized_end=1489 + _ERROR._serialized_start=1491 + _ERROR._serialized_end=1569 + _DECIMAL64._serialized_start=1571 + _DECIMAL64._serialized_end=1621 + _SCALARARRAY._serialized_start=1623 + _SCALARARRAY._serialized_end=1671 + _SUBSCRIBEREQUEST._serialized_start=1674 + _SUBSCRIBEREQUEST._serialized_end=1827 + _POLL._serialized_start=1829 + _POLL._serialized_end=1835 + _SUBSCRIBERESPONSE._serialized_start=1838 + _SUBSCRIBERESPONSE._serialized_end=2002 + _SUBSCRIPTIONLIST._serialized_start=2005 + _SUBSCRIPTIONLIST._serialized_end=2346 + _SUBSCRIPTIONLIST_MODE._serialized_start=2289 + _SUBSCRIPTIONLIST_MODE._serialized_end=2327 + _SUBSCRIPTION._serialized_start=2349 + _SUBSCRIPTION._serialized_end=2508 + _QOSMARKING._serialized_start=2510 + _QOSMARKING._serialized_end=2539 + _SETREQUEST._serialized_start=2542 + _SETREQUEST._serialized_end=2707 + _SETRESPONSE._serialized_start=2710 + _SETRESPONSE._serialized_end=2878 + _UPDATERESULT._serialized_start=2881 + _UPDATERESULT._serialized_end=3083 + _UPDATERESULT_OPERATION._serialized_start=3022 + _UPDATERESULT_OPERATION._serialized_end=3083 + _GETREQUEST._serialized_start=3086 + _GETREQUEST._serialized_end=3361 + _GETREQUEST_DATATYPE._serialized_start=3302 + _GETREQUEST_DATATYPE._serialized_end=3361 + _GETRESPONSE._serialized_start=3363 + _GETRESPONSE._serialized_end=3486 + _CAPABILITYREQUEST._serialized_start=3488 + _CAPABILITYREQUEST._serialized_end=3543 + _CAPABILITYRESPONSE._serialized_start=3546 + _CAPABILITYRESPONSE._serialized_end=3712 + _MODELDATA._serialized_start=3714 + _MODELDATA._serialized_end=3778 + _GNMI._serialized_start=3971 + _GNMI._serialized_end=4198 +# @@protoc_insertion_point(module_scope) diff --git a/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_pb2.py.old b/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_pb2.py.old new file mode 100644 index 0000000000000000000000000000000000000000..313674f8ce5be1586c63df02226570f5082544ba --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_pb2.py.old @@ -0,0 +1,2037 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: proto/gnmi/gnmi.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 +from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='proto/gnmi/gnmi.proto', + package='gnmi', + syntax='proto3', + serialized_pb=_b('\n\x15proto/gnmi/gnmi.proto\x12\x04gnmi\x1a\x19google/protobuf/any.proto\x1a google/protobuf/descriptor.proto\"\x86\x01\n\x0cNotification\x12\x11\n\ttimestamp\x18\x01 \x01(\x03\x12\x1a\n\x06prefix\x18\x02 \x01(\x0b\x32\n.gnmi.Path\x12\r\n\x05\x61lias\x18\x03 \x01(\t\x12\x1c\n\x06update\x18\x04 \x03(\x0b\x32\x0c.gnmi.Update\x12\x1a\n\x06\x64\x65lete\x18\x05 \x03(\x0b\x32\n.gnmi.Path\"u\n\x06Update\x12\x18\n\x04path\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0b.gnmi.ValueB\x02\x18\x01\x12\x1d\n\x03val\x18\x03 \x01(\x0b\x32\x10.gnmi.TypedValue\x12\x12\n\nduplicates\x18\x04 \x01(\r\"\xce\x02\n\nTypedValue\x12\x14\n\nstring_val\x18\x01 \x01(\tH\x00\x12\x11\n\x07int_val\x18\x02 \x01(\x03H\x00\x12\x12\n\x08uint_val\x18\x03 \x01(\x04H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x13\n\tbytes_val\x18\x05 \x01(\x0cH\x00\x12\x13\n\tfloat_val\x18\x06 \x01(\x02H\x00\x12&\n\x0b\x64\x65\x63imal_val\x18\x07 \x01(\x0b\x32\x0f.gnmi.Decimal64H\x00\x12)\n\x0cleaflist_val\x18\x08 \x01(\x0b\x32\x11.gnmi.ScalarArrayH\x00\x12\'\n\x07\x61ny_val\x18\t \x01(\x0b\x32\x14.google.protobuf.AnyH\x00\x12\x12\n\x08json_val\x18\n \x01(\x0cH\x00\x12\x17\n\rjson_ietf_val\x18\x0b \x01(\x0cH\x00\x12\x13\n\tascii_val\x18\x0c \x01(\tH\x00\x42\x07\n\x05value\"Y\n\x04Path\x12\x13\n\x07\x65lement\x18\x01 \x03(\tB\x02\x18\x01\x12\x0e\n\x06origin\x18\x02 \x01(\t\x12\x1c\n\x04\x65lem\x18\x03 \x03(\x0b\x32\x0e.gnmi.PathElem\x12\x0e\n\x06target\x18\x04 \x01(\t\"j\n\x08PathElem\x12\x0c\n\x04name\x18\x01 \x01(\t\x12$\n\x03key\x18\x02 \x03(\x0b\x32\x17.gnmi.PathElem.KeyEntry\x1a*\n\x08KeyEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"8\n\x05Value\x12\r\n\x05value\x18\x01 \x01(\x0c\x12\x1c\n\x04type\x18\x02 \x01(\x0e\x32\x0e.gnmi.Encoding:\x02\x18\x01\"N\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x01(\r\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\"\n\x04\x64\x61ta\x18\x03 \x01(\x0b\x32\x14.google.protobuf.Any:\x02\x18\x01\".\n\tDecimal64\x12\x0e\n\x06\x64igits\x18\x01 \x01(\x04\x12\x11\n\tprecision\x18\x02 \x01(\r\"0\n\x0bScalarArray\x12!\n\x07\x65lement\x18\x01 \x03(\x0b\x32\x10.gnmi.TypedValue\"\x8a\x01\n\x10SubscribeRequest\x12+\n\tsubscribe\x18\x01 \x01(\x0b\x32\x16.gnmi.SubscriptionListH\x00\x12\x1a\n\x04poll\x18\x03 \x01(\x0b\x32\n.gnmi.PollH\x00\x12\"\n\x07\x61liases\x18\x04 \x01(\x0b\x32\x0f.gnmi.AliasListH\x00\x42\t\n\x07request\"\x06\n\x04Poll\"\x80\x01\n\x11SubscribeResponse\x12$\n\x06update\x18\x01 \x01(\x0b\x32\x12.gnmi.NotificationH\x00\x12\x17\n\rsync_response\x18\x03 \x01(\x08H\x00\x12 \n\x05\x65rror\x18\x04 \x01(\x0b\x32\x0b.gnmi.ErrorB\x02\x18\x01H\x00\x42\n\n\x08response\"\xd7\x02\n\x10SubscriptionList\x12\x1a\n\x06prefix\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12(\n\x0csubscription\x18\x02 \x03(\x0b\x32\x12.gnmi.Subscription\x12\x13\n\x0buse_aliases\x18\x03 \x01(\x08\x12\x1d\n\x03qos\x18\x04 \x01(\x0b\x32\x10.gnmi.QOSMarking\x12)\n\x04mode\x18\x05 \x01(\x0e\x32\x1b.gnmi.SubscriptionList.Mode\x12\x19\n\x11\x61llow_aggregation\x18\x06 \x01(\x08\x12#\n\nuse_models\x18\x07 \x03(\x0b\x32\x0f.gnmi.ModelData\x12 \n\x08\x65ncoding\x18\x08 \x01(\x0e\x32\x0e.gnmi.Encoding\x12\x14\n\x0cupdates_only\x18\t \x01(\x08\"&\n\x04Mode\x12\n\n\x06STREAM\x10\x00\x12\x08\n\x04ONCE\x10\x01\x12\x08\n\x04POLL\x10\x02\"\x9f\x01\n\x0cSubscription\x12\x18\n\x04path\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12$\n\x04mode\x18\x02 \x01(\x0e\x32\x16.gnmi.SubscriptionMode\x12\x17\n\x0fsample_interval\x18\x03 \x01(\x04\x12\x1a\n\x12suppress_redundant\x18\x04 \x01(\x08\x12\x1a\n\x12heartbeat_interval\x18\x05 \x01(\x04\"\x1d\n\nQOSMarking\x12\x0f\n\x07marking\x18\x01 \x01(\r\"0\n\x05\x41lias\x12\x18\n\x04path\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12\r\n\x05\x61lias\x18\x02 \x01(\t\"\'\n\tAliasList\x12\x1a\n\x05\x61lias\x18\x01 \x03(\x0b\x32\x0b.gnmi.Alias\"\x81\x01\n\nSetRequest\x12\x1a\n\x06prefix\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12\x1a\n\x06\x64\x65lete\x18\x02 \x03(\x0b\x32\n.gnmi.Path\x12\x1d\n\x07replace\x18\x03 \x03(\x0b\x32\x0c.gnmi.Update\x12\x1c\n\x06update\x18\x04 \x03(\x0b\x32\x0c.gnmi.Update\"\x84\x01\n\x0bSetResponse\x12\x1a\n\x06prefix\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12$\n\x08response\x18\x02 \x03(\x0b\x32\x12.gnmi.UpdateResult\x12 \n\x07message\x18\x03 \x01(\x0b\x32\x0b.gnmi.ErrorB\x02\x18\x01\x12\x11\n\ttimestamp\x18\x04 \x01(\x03\"\xca\x01\n\x0cUpdateResult\x12\x15\n\ttimestamp\x18\x01 \x01(\x03\x42\x02\x18\x01\x12\x18\n\x04path\x18\x02 \x01(\x0b\x32\n.gnmi.Path\x12 \n\x07message\x18\x03 \x01(\x0b\x32\x0b.gnmi.ErrorB\x02\x18\x01\x12(\n\x02op\x18\x04 \x01(\x0e\x32\x1c.gnmi.UpdateResult.Operation\"=\n\tOperation\x12\x0b\n\x07INVALID\x10\x00\x12\n\n\x06\x44\x45LETE\x10\x01\x12\x0b\n\x07REPLACE\x10\x02\x12\n\n\x06UPDATE\x10\x03\"\xef\x01\n\nGetRequest\x12\x1a\n\x06prefix\x18\x01 \x01(\x0b\x32\n.gnmi.Path\x12\x18\n\x04path\x18\x02 \x03(\x0b\x32\n.gnmi.Path\x12\'\n\x04type\x18\x03 \x01(\x0e\x32\x19.gnmi.GetRequest.DataType\x12 \n\x08\x65ncoding\x18\x05 \x01(\x0e\x32\x0e.gnmi.Encoding\x12#\n\nuse_models\x18\x06 \x03(\x0b\x32\x0f.gnmi.ModelData\";\n\x08\x44\x61taType\x12\x07\n\x03\x41LL\x10\x00\x12\n\n\x06\x43ONFIG\x10\x01\x12\t\n\x05STATE\x10\x02\x12\x0f\n\x0bOPERATIONAL\x10\x03\"W\n\x0bGetResponse\x12(\n\x0cnotification\x18\x01 \x03(\x0b\x32\x12.gnmi.Notification\x12\x1e\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x0b.gnmi.ErrorB\x02\x18\x01\"\x13\n\x11\x43\x61pabilityRequest\"\x82\x01\n\x12\x43\x61pabilityResponse\x12)\n\x10supported_models\x18\x01 \x03(\x0b\x32\x0f.gnmi.ModelData\x12+\n\x13supported_encodings\x18\x02 \x03(\x0e\x32\x0e.gnmi.Encoding\x12\x14\n\x0cgNMI_version\x18\x03 \x01(\t\"@\n\tModelData\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0corganization\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t*D\n\x08\x45ncoding\x12\x08\n\x04JSON\x10\x00\x12\t\n\x05\x42YTES\x10\x01\x12\t\n\x05PROTO\x10\x02\x12\t\n\x05\x41SCII\x10\x03\x12\r\n\tJSON_IETF\x10\x04*A\n\x10SubscriptionMode\x12\x12\n\x0eTARGET_DEFINED\x10\x00\x12\r\n\tON_CHANGE\x10\x01\x12\n\n\x06SAMPLE\x10\x02\x32\xe3\x01\n\x04gNMI\x12\x41\n\x0c\x43\x61pabilities\x12\x17.gnmi.CapabilityRequest\x1a\x18.gnmi.CapabilityResponse\x12*\n\x03Get\x12\x10.gnmi.GetRequest\x1a\x11.gnmi.GetResponse\x12*\n\x03Set\x12\x10.gnmi.SetRequest\x1a\x11.gnmi.SetResponse\x12@\n\tSubscribe\x12\x16.gnmi.SubscribeRequest\x1a\x17.gnmi.SubscribeResponse(\x01\x30\x01:3\n\x0cgnmi_service\x12\x1c.google.protobuf.FileOptions\x18\xe9\x07 \x01(\tB\x08\xca>\x05\x30.5.0b\x06proto3') + , + dependencies=[google_dot_protobuf_dot_any__pb2.DESCRIPTOR,google_dot_protobuf_dot_descriptor__pb2.DESCRIPTOR,]) + +_ENCODING = _descriptor.EnumDescriptor( + name='Encoding', + full_name='gnmi.Encoding', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='JSON', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='BYTES', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='PROTO', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ASCII', index=3, number=3, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='JSON_IETF', index=4, number=4, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=3053, + serialized_end=3121, +) +_sym_db.RegisterEnumDescriptor(_ENCODING) + +Encoding = enum_type_wrapper.EnumTypeWrapper(_ENCODING) +_SUBSCRIPTIONMODE = _descriptor.EnumDescriptor( + name='SubscriptionMode', + full_name='gnmi.SubscriptionMode', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='TARGET_DEFINED', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ON_CHANGE', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SAMPLE', index=2, number=2, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=3123, + serialized_end=3188, +) +_sym_db.RegisterEnumDescriptor(_SUBSCRIPTIONMODE) + +SubscriptionMode = enum_type_wrapper.EnumTypeWrapper(_SUBSCRIPTIONMODE) +JSON = 0 +BYTES = 1 +PROTO = 2 +ASCII = 3 +JSON_IETF = 4 +TARGET_DEFINED = 0 +ON_CHANGE = 1 +SAMPLE = 2 + +GNMI_SERVICE_FIELD_NUMBER = 1001 +gnmi_service = _descriptor.FieldDescriptor( + name='gnmi_service', full_name='gnmi.gnmi_service', index=0, + number=1001, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=True, extension_scope=None, + options=None) + +_SUBSCRIPTIONLIST_MODE = _descriptor.EnumDescriptor( + name='Mode', + full_name='gnmi.SubscriptionList.Mode', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='STREAM', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ONCE', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='POLL', index=2, number=2, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=1706, + serialized_end=1744, +) +_sym_db.RegisterEnumDescriptor(_SUBSCRIPTIONLIST_MODE) + +_UPDATERESULT_OPERATION = _descriptor.EnumDescriptor( + name='Operation', + full_name='gnmi.UpdateResult.Operation', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='INVALID', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DELETE', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='REPLACE', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='UPDATE', index=3, number=3, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=2439, + serialized_end=2500, +) +_sym_db.RegisterEnumDescriptor(_UPDATERESULT_OPERATION) + +_GETREQUEST_DATATYPE = _descriptor.EnumDescriptor( + name='DataType', + full_name='gnmi.GetRequest.DataType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='ALL', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='CONFIG', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='STATE', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='OPERATIONAL', index=3, number=3, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=2683, + serialized_end=2742, +) +_sym_db.RegisterEnumDescriptor(_GETREQUEST_DATATYPE) + + +_NOTIFICATION = _descriptor.Descriptor( + name='Notification', + full_name='gnmi.Notification', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='timestamp', full_name='gnmi.Notification.timestamp', index=0, + number=1, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='prefix', full_name='gnmi.Notification.prefix', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='alias', full_name='gnmi.Notification.alias', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='update', full_name='gnmi.Notification.update', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='delete', full_name='gnmi.Notification.delete', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=93, + serialized_end=227, +) + + +_UPDATE = _descriptor.Descriptor( + name='Update', + full_name='gnmi.Update', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='path', full_name='gnmi.Update.path', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='value', full_name='gnmi.Update.value', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001'))), + _descriptor.FieldDescriptor( + name='val', full_name='gnmi.Update.val', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='duplicates', full_name='gnmi.Update.duplicates', index=3, + number=4, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=229, + serialized_end=346, +) + + +_TYPEDVALUE = _descriptor.Descriptor( + name='TypedValue', + full_name='gnmi.TypedValue', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='string_val', full_name='gnmi.TypedValue.string_val', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='int_val', full_name='gnmi.TypedValue.int_val', index=1, + number=2, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='uint_val', full_name='gnmi.TypedValue.uint_val', index=2, + number=3, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='bool_val', full_name='gnmi.TypedValue.bool_val', index=3, + number=4, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='bytes_val', full_name='gnmi.TypedValue.bytes_val', index=4, + number=5, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='float_val', full_name='gnmi.TypedValue.float_val', index=5, + number=6, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='decimal_val', full_name='gnmi.TypedValue.decimal_val', index=6, + number=7, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='leaflist_val', full_name='gnmi.TypedValue.leaflist_val', index=7, + number=8, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='any_val', full_name='gnmi.TypedValue.any_val', index=8, + number=9, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='json_val', full_name='gnmi.TypedValue.json_val', index=9, + number=10, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='json_ietf_val', full_name='gnmi.TypedValue.json_ietf_val', index=10, + number=11, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='ascii_val', full_name='gnmi.TypedValue.ascii_val', index=11, + number=12, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='value', full_name='gnmi.TypedValue.value', + index=0, containing_type=None, fields=[]), + ], + serialized_start=349, + serialized_end=683, +) + + +_PATH = _descriptor.Descriptor( + name='Path', + full_name='gnmi.Path', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='element', full_name='gnmi.Path.element', index=0, + number=1, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001'))), + _descriptor.FieldDescriptor( + name='origin', full_name='gnmi.Path.origin', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='elem', full_name='gnmi.Path.elem', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='target', full_name='gnmi.Path.target', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=685, + serialized_end=774, +) + + +_PATHELEM_KEYENTRY = _descriptor.Descriptor( + name='KeyEntry', + full_name='gnmi.PathElem.KeyEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='gnmi.PathElem.KeyEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='value', full_name='gnmi.PathElem.KeyEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=840, + serialized_end=882, +) + +_PATHELEM = _descriptor.Descriptor( + name='PathElem', + full_name='gnmi.PathElem', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='gnmi.PathElem.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='key', full_name='gnmi.PathElem.key', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_PATHELEM_KEYENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=776, + serialized_end=882, +) + + +_VALUE = _descriptor.Descriptor( + name='Value', + full_name='gnmi.Value', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='value', full_name='gnmi.Value.value', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='type', full_name='gnmi.Value.type', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('\030\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=884, + serialized_end=940, +) + + +_ERROR = _descriptor.Descriptor( + name='Error', + full_name='gnmi.Error', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='code', full_name='gnmi.Error.code', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='message', full_name='gnmi.Error.message', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='data', full_name='gnmi.Error.data', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('\030\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=942, + serialized_end=1020, +) + + +_DECIMAL64 = _descriptor.Descriptor( + name='Decimal64', + full_name='gnmi.Decimal64', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='digits', full_name='gnmi.Decimal64.digits', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='precision', full_name='gnmi.Decimal64.precision', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1022, + serialized_end=1068, +) + + +_SCALARARRAY = _descriptor.Descriptor( + name='ScalarArray', + full_name='gnmi.ScalarArray', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='element', full_name='gnmi.ScalarArray.element', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1070, + serialized_end=1118, +) + + +_SUBSCRIBEREQUEST = _descriptor.Descriptor( + name='SubscribeRequest', + full_name='gnmi.SubscribeRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='subscribe', full_name='gnmi.SubscribeRequest.subscribe', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='poll', full_name='gnmi.SubscribeRequest.poll', index=1, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='aliases', full_name='gnmi.SubscribeRequest.aliases', index=2, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='request', full_name='gnmi.SubscribeRequest.request', + index=0, containing_type=None, fields=[]), + ], + serialized_start=1121, + serialized_end=1259, +) + + +_POLL = _descriptor.Descriptor( + name='Poll', + full_name='gnmi.Poll', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1261, + serialized_end=1267, +) + + +_SUBSCRIBERESPONSE = _descriptor.Descriptor( + name='SubscribeResponse', + full_name='gnmi.SubscribeResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='update', full_name='gnmi.SubscribeResponse.update', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='sync_response', full_name='gnmi.SubscribeResponse.sync_response', index=1, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='error', full_name='gnmi.SubscribeResponse.error', index=2, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001'))), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='response', full_name='gnmi.SubscribeResponse.response', + index=0, containing_type=None, fields=[]), + ], + serialized_start=1270, + serialized_end=1398, +) + + +_SUBSCRIPTIONLIST = _descriptor.Descriptor( + name='SubscriptionList', + full_name='gnmi.SubscriptionList', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='prefix', full_name='gnmi.SubscriptionList.prefix', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='subscription', full_name='gnmi.SubscriptionList.subscription', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='use_aliases', full_name='gnmi.SubscriptionList.use_aliases', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='qos', full_name='gnmi.SubscriptionList.qos', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mode', full_name='gnmi.SubscriptionList.mode', index=4, + number=5, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='allow_aggregation', full_name='gnmi.SubscriptionList.allow_aggregation', index=5, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='use_models', full_name='gnmi.SubscriptionList.use_models', index=6, + number=7, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='encoding', full_name='gnmi.SubscriptionList.encoding', index=7, + number=8, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='updates_only', full_name='gnmi.SubscriptionList.updates_only', index=8, + number=9, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _SUBSCRIPTIONLIST_MODE, + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1401, + serialized_end=1744, +) + + +_SUBSCRIPTION = _descriptor.Descriptor( + name='Subscription', + full_name='gnmi.Subscription', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='path', full_name='gnmi.Subscription.path', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mode', full_name='gnmi.Subscription.mode', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='sample_interval', full_name='gnmi.Subscription.sample_interval', index=2, + number=3, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='suppress_redundant', full_name='gnmi.Subscription.suppress_redundant', index=3, + number=4, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='heartbeat_interval', full_name='gnmi.Subscription.heartbeat_interval', index=4, + number=5, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1747, + serialized_end=1906, +) + + +_QOSMARKING = _descriptor.Descriptor( + name='QOSMarking', + full_name='gnmi.QOSMarking', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='marking', full_name='gnmi.QOSMarking.marking', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1908, + serialized_end=1937, +) + + +_ALIAS = _descriptor.Descriptor( + name='Alias', + full_name='gnmi.Alias', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='path', full_name='gnmi.Alias.path', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='alias', full_name='gnmi.Alias.alias', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1939, + serialized_end=1987, +) + + +_ALIASLIST = _descriptor.Descriptor( + name='AliasList', + full_name='gnmi.AliasList', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='alias', full_name='gnmi.AliasList.alias', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1989, + serialized_end=2028, +) + + +_SETREQUEST = _descriptor.Descriptor( + name='SetRequest', + full_name='gnmi.SetRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='prefix', full_name='gnmi.SetRequest.prefix', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='delete', full_name='gnmi.SetRequest.delete', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='replace', full_name='gnmi.SetRequest.replace', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='update', full_name='gnmi.SetRequest.update', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2031, + serialized_end=2160, +) + + +_SETRESPONSE = _descriptor.Descriptor( + name='SetResponse', + full_name='gnmi.SetResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='prefix', full_name='gnmi.SetResponse.prefix', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='response', full_name='gnmi.SetResponse.response', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='message', full_name='gnmi.SetResponse.message', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001'))), + _descriptor.FieldDescriptor( + name='timestamp', full_name='gnmi.SetResponse.timestamp', index=3, + number=4, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2163, + serialized_end=2295, +) + + +_UPDATERESULT = _descriptor.Descriptor( + name='UpdateResult', + full_name='gnmi.UpdateResult', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='timestamp', full_name='gnmi.UpdateResult.timestamp', index=0, + number=1, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001'))), + _descriptor.FieldDescriptor( + name='path', full_name='gnmi.UpdateResult.path', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='message', full_name='gnmi.UpdateResult.message', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001'))), + _descriptor.FieldDescriptor( + name='op', full_name='gnmi.UpdateResult.op', index=3, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _UPDATERESULT_OPERATION, + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2298, + serialized_end=2500, +) + + +_GETREQUEST = _descriptor.Descriptor( + name='GetRequest', + full_name='gnmi.GetRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='prefix', full_name='gnmi.GetRequest.prefix', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='path', full_name='gnmi.GetRequest.path', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='type', full_name='gnmi.GetRequest.type', index=2, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='encoding', full_name='gnmi.GetRequest.encoding', index=3, + number=5, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='use_models', full_name='gnmi.GetRequest.use_models', index=4, + number=6, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _GETREQUEST_DATATYPE, + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2503, + serialized_end=2742, +) + + +_GETRESPONSE = _descriptor.Descriptor( + name='GetResponse', + full_name='gnmi.GetResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='notification', full_name='gnmi.GetResponse.notification', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='error', full_name='gnmi.GetResponse.error', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001'))), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2744, + serialized_end=2831, +) + + +_CAPABILITYREQUEST = _descriptor.Descriptor( + name='CapabilityRequest', + full_name='gnmi.CapabilityRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2833, + serialized_end=2852, +) + + +_CAPABILITYRESPONSE = _descriptor.Descriptor( + name='CapabilityResponse', + full_name='gnmi.CapabilityResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='supported_models', full_name='gnmi.CapabilityResponse.supported_models', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='supported_encodings', full_name='gnmi.CapabilityResponse.supported_encodings', index=1, + number=2, type=14, cpp_type=8, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='gNMI_version', full_name='gnmi.CapabilityResponse.gNMI_version', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2855, + serialized_end=2985, +) + + +_MODELDATA = _descriptor.Descriptor( + name='ModelData', + full_name='gnmi.ModelData', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='gnmi.ModelData.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='organization', full_name='gnmi.ModelData.organization', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='version', full_name='gnmi.ModelData.version', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2987, + serialized_end=3051, +) + +_NOTIFICATION.fields_by_name['prefix'].message_type = _PATH +_NOTIFICATION.fields_by_name['update'].message_type = _UPDATE +_NOTIFICATION.fields_by_name['delete'].message_type = _PATH +_UPDATE.fields_by_name['path'].message_type = _PATH +_UPDATE.fields_by_name['value'].message_type = _VALUE +_UPDATE.fields_by_name['val'].message_type = _TYPEDVALUE +_TYPEDVALUE.fields_by_name['decimal_val'].message_type = _DECIMAL64 +_TYPEDVALUE.fields_by_name['leaflist_val'].message_type = _SCALARARRAY +_TYPEDVALUE.fields_by_name['any_val'].message_type = google_dot_protobuf_dot_any__pb2._ANY +_TYPEDVALUE.oneofs_by_name['value'].fields.append( + _TYPEDVALUE.fields_by_name['string_val']) +_TYPEDVALUE.fields_by_name['string_val'].containing_oneof = _TYPEDVALUE.oneofs_by_name['value'] +_TYPEDVALUE.oneofs_by_name['value'].fields.append( + _TYPEDVALUE.fields_by_name['int_val']) +_TYPEDVALUE.fields_by_name['int_val'].containing_oneof = _TYPEDVALUE.oneofs_by_name['value'] +_TYPEDVALUE.oneofs_by_name['value'].fields.append( + _TYPEDVALUE.fields_by_name['uint_val']) +_TYPEDVALUE.fields_by_name['uint_val'].containing_oneof = _TYPEDVALUE.oneofs_by_name['value'] +_TYPEDVALUE.oneofs_by_name['value'].fields.append( + _TYPEDVALUE.fields_by_name['bool_val']) +_TYPEDVALUE.fields_by_name['bool_val'].containing_oneof = _TYPEDVALUE.oneofs_by_name['value'] +_TYPEDVALUE.oneofs_by_name['value'].fields.append( + _TYPEDVALUE.fields_by_name['bytes_val']) +_TYPEDVALUE.fields_by_name['bytes_val'].containing_oneof = _TYPEDVALUE.oneofs_by_name['value'] +_TYPEDVALUE.oneofs_by_name['value'].fields.append( + _TYPEDVALUE.fields_by_name['float_val']) +_TYPEDVALUE.fields_by_name['float_val'].containing_oneof = _TYPEDVALUE.oneofs_by_name['value'] +_TYPEDVALUE.oneofs_by_name['value'].fields.append( + _TYPEDVALUE.fields_by_name['decimal_val']) +_TYPEDVALUE.fields_by_name['decimal_val'].containing_oneof = _TYPEDVALUE.oneofs_by_name['value'] +_TYPEDVALUE.oneofs_by_name['value'].fields.append( + _TYPEDVALUE.fields_by_name['leaflist_val']) +_TYPEDVALUE.fields_by_name['leaflist_val'].containing_oneof = _TYPEDVALUE.oneofs_by_name['value'] +_TYPEDVALUE.oneofs_by_name['value'].fields.append( + _TYPEDVALUE.fields_by_name['any_val']) +_TYPEDVALUE.fields_by_name['any_val'].containing_oneof = _TYPEDVALUE.oneofs_by_name['value'] +_TYPEDVALUE.oneofs_by_name['value'].fields.append( + _TYPEDVALUE.fields_by_name['json_val']) +_TYPEDVALUE.fields_by_name['json_val'].containing_oneof = _TYPEDVALUE.oneofs_by_name['value'] +_TYPEDVALUE.oneofs_by_name['value'].fields.append( + _TYPEDVALUE.fields_by_name['json_ietf_val']) +_TYPEDVALUE.fields_by_name['json_ietf_val'].containing_oneof = _TYPEDVALUE.oneofs_by_name['value'] +_TYPEDVALUE.oneofs_by_name['value'].fields.append( + _TYPEDVALUE.fields_by_name['ascii_val']) +_TYPEDVALUE.fields_by_name['ascii_val'].containing_oneof = _TYPEDVALUE.oneofs_by_name['value'] +_PATH.fields_by_name['elem'].message_type = _PATHELEM +_PATHELEM_KEYENTRY.containing_type = _PATHELEM +_PATHELEM.fields_by_name['key'].message_type = _PATHELEM_KEYENTRY +_VALUE.fields_by_name['type'].enum_type = _ENCODING +_ERROR.fields_by_name['data'].message_type = google_dot_protobuf_dot_any__pb2._ANY +_SCALARARRAY.fields_by_name['element'].message_type = _TYPEDVALUE +_SUBSCRIBEREQUEST.fields_by_name['subscribe'].message_type = _SUBSCRIPTIONLIST +_SUBSCRIBEREQUEST.fields_by_name['poll'].message_type = _POLL +_SUBSCRIBEREQUEST.fields_by_name['aliases'].message_type = _ALIASLIST +_SUBSCRIBEREQUEST.oneofs_by_name['request'].fields.append( + _SUBSCRIBEREQUEST.fields_by_name['subscribe']) +_SUBSCRIBEREQUEST.fields_by_name['subscribe'].containing_oneof = _SUBSCRIBEREQUEST.oneofs_by_name['request'] +_SUBSCRIBEREQUEST.oneofs_by_name['request'].fields.append( + _SUBSCRIBEREQUEST.fields_by_name['poll']) +_SUBSCRIBEREQUEST.fields_by_name['poll'].containing_oneof = _SUBSCRIBEREQUEST.oneofs_by_name['request'] +_SUBSCRIBEREQUEST.oneofs_by_name['request'].fields.append( + _SUBSCRIBEREQUEST.fields_by_name['aliases']) +_SUBSCRIBEREQUEST.fields_by_name['aliases'].containing_oneof = _SUBSCRIBEREQUEST.oneofs_by_name['request'] +_SUBSCRIBERESPONSE.fields_by_name['update'].message_type = _NOTIFICATION +_SUBSCRIBERESPONSE.fields_by_name['error'].message_type = _ERROR +_SUBSCRIBERESPONSE.oneofs_by_name['response'].fields.append( + _SUBSCRIBERESPONSE.fields_by_name['update']) +_SUBSCRIBERESPONSE.fields_by_name['update'].containing_oneof = _SUBSCRIBERESPONSE.oneofs_by_name['response'] +_SUBSCRIBERESPONSE.oneofs_by_name['response'].fields.append( + _SUBSCRIBERESPONSE.fields_by_name['sync_response']) +_SUBSCRIBERESPONSE.fields_by_name['sync_response'].containing_oneof = _SUBSCRIBERESPONSE.oneofs_by_name['response'] +_SUBSCRIBERESPONSE.oneofs_by_name['response'].fields.append( + _SUBSCRIBERESPONSE.fields_by_name['error']) +_SUBSCRIBERESPONSE.fields_by_name['error'].containing_oneof = _SUBSCRIBERESPONSE.oneofs_by_name['response'] +_SUBSCRIPTIONLIST.fields_by_name['prefix'].message_type = _PATH +_SUBSCRIPTIONLIST.fields_by_name['subscription'].message_type = _SUBSCRIPTION +_SUBSCRIPTIONLIST.fields_by_name['qos'].message_type = _QOSMARKING +_SUBSCRIPTIONLIST.fields_by_name['mode'].enum_type = _SUBSCRIPTIONLIST_MODE +_SUBSCRIPTIONLIST.fields_by_name['use_models'].message_type = _MODELDATA +_SUBSCRIPTIONLIST.fields_by_name['encoding'].enum_type = _ENCODING +_SUBSCRIPTIONLIST_MODE.containing_type = _SUBSCRIPTIONLIST +_SUBSCRIPTION.fields_by_name['path'].message_type = _PATH +_SUBSCRIPTION.fields_by_name['mode'].enum_type = _SUBSCRIPTIONMODE +_ALIAS.fields_by_name['path'].message_type = _PATH +_ALIASLIST.fields_by_name['alias'].message_type = _ALIAS +_SETREQUEST.fields_by_name['prefix'].message_type = _PATH +_SETREQUEST.fields_by_name['delete'].message_type = _PATH +_SETREQUEST.fields_by_name['replace'].message_type = _UPDATE +_SETREQUEST.fields_by_name['update'].message_type = _UPDATE +_SETRESPONSE.fields_by_name['prefix'].message_type = _PATH +_SETRESPONSE.fields_by_name['response'].message_type = _UPDATERESULT +_SETRESPONSE.fields_by_name['message'].message_type = _ERROR +_UPDATERESULT.fields_by_name['path'].message_type = _PATH +_UPDATERESULT.fields_by_name['message'].message_type = _ERROR +_UPDATERESULT.fields_by_name['op'].enum_type = _UPDATERESULT_OPERATION +_UPDATERESULT_OPERATION.containing_type = _UPDATERESULT +_GETREQUEST.fields_by_name['prefix'].message_type = _PATH +_GETREQUEST.fields_by_name['path'].message_type = _PATH +_GETREQUEST.fields_by_name['type'].enum_type = _GETREQUEST_DATATYPE +_GETREQUEST.fields_by_name['encoding'].enum_type = _ENCODING +_GETREQUEST.fields_by_name['use_models'].message_type = _MODELDATA +_GETREQUEST_DATATYPE.containing_type = _GETREQUEST +_GETRESPONSE.fields_by_name['notification'].message_type = _NOTIFICATION +_GETRESPONSE.fields_by_name['error'].message_type = _ERROR +_CAPABILITYRESPONSE.fields_by_name['supported_models'].message_type = _MODELDATA +_CAPABILITYRESPONSE.fields_by_name['supported_encodings'].enum_type = _ENCODING +DESCRIPTOR.message_types_by_name['Notification'] = _NOTIFICATION +DESCRIPTOR.message_types_by_name['Update'] = _UPDATE +DESCRIPTOR.message_types_by_name['TypedValue'] = _TYPEDVALUE +DESCRIPTOR.message_types_by_name['Path'] = _PATH +DESCRIPTOR.message_types_by_name['PathElem'] = _PATHELEM +DESCRIPTOR.message_types_by_name['Value'] = _VALUE +DESCRIPTOR.message_types_by_name['Error'] = _ERROR +DESCRIPTOR.message_types_by_name['Decimal64'] = _DECIMAL64 +DESCRIPTOR.message_types_by_name['ScalarArray'] = _SCALARARRAY +DESCRIPTOR.message_types_by_name['SubscribeRequest'] = _SUBSCRIBEREQUEST +DESCRIPTOR.message_types_by_name['Poll'] = _POLL +DESCRIPTOR.message_types_by_name['SubscribeResponse'] = _SUBSCRIBERESPONSE +DESCRIPTOR.message_types_by_name['SubscriptionList'] = _SUBSCRIPTIONLIST +DESCRIPTOR.message_types_by_name['Subscription'] = _SUBSCRIPTION +DESCRIPTOR.message_types_by_name['QOSMarking'] = _QOSMARKING +DESCRIPTOR.message_types_by_name['Alias'] = _ALIAS +DESCRIPTOR.message_types_by_name['AliasList'] = _ALIASLIST +DESCRIPTOR.message_types_by_name['SetRequest'] = _SETREQUEST +DESCRIPTOR.message_types_by_name['SetResponse'] = _SETRESPONSE +DESCRIPTOR.message_types_by_name['UpdateResult'] = _UPDATERESULT +DESCRIPTOR.message_types_by_name['GetRequest'] = _GETREQUEST +DESCRIPTOR.message_types_by_name['GetResponse'] = _GETRESPONSE +DESCRIPTOR.message_types_by_name['CapabilityRequest'] = _CAPABILITYREQUEST +DESCRIPTOR.message_types_by_name['CapabilityResponse'] = _CAPABILITYRESPONSE +DESCRIPTOR.message_types_by_name['ModelData'] = _MODELDATA +DESCRIPTOR.enum_types_by_name['Encoding'] = _ENCODING +DESCRIPTOR.enum_types_by_name['SubscriptionMode'] = _SUBSCRIPTIONMODE +DESCRIPTOR.extensions_by_name['gnmi_service'] = gnmi_service +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +Notification = _reflection.GeneratedProtocolMessageType('Notification', (_message.Message,), dict( + DESCRIPTOR = _NOTIFICATION, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.Notification) + )) +_sym_db.RegisterMessage(Notification) + +Update = _reflection.GeneratedProtocolMessageType('Update', (_message.Message,), dict( + DESCRIPTOR = _UPDATE, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.Update) + )) +_sym_db.RegisterMessage(Update) + +TypedValue = _reflection.GeneratedProtocolMessageType('TypedValue', (_message.Message,), dict( + DESCRIPTOR = _TYPEDVALUE, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.TypedValue) + )) +_sym_db.RegisterMessage(TypedValue) + +Path = _reflection.GeneratedProtocolMessageType('Path', (_message.Message,), dict( + DESCRIPTOR = _PATH, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.Path) + )) +_sym_db.RegisterMessage(Path) + +PathElem = _reflection.GeneratedProtocolMessageType('PathElem', (_message.Message,), dict( + + KeyEntry = _reflection.GeneratedProtocolMessageType('KeyEntry', (_message.Message,), dict( + DESCRIPTOR = _PATHELEM_KEYENTRY, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.PathElem.KeyEntry) + )) + , + DESCRIPTOR = _PATHELEM, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.PathElem) + )) +_sym_db.RegisterMessage(PathElem) +_sym_db.RegisterMessage(PathElem.KeyEntry) + +Value = _reflection.GeneratedProtocolMessageType('Value', (_message.Message,), dict( + DESCRIPTOR = _VALUE, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.Value) + )) +_sym_db.RegisterMessage(Value) + +Error = _reflection.GeneratedProtocolMessageType('Error', (_message.Message,), dict( + DESCRIPTOR = _ERROR, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.Error) + )) +_sym_db.RegisterMessage(Error) + +Decimal64 = _reflection.GeneratedProtocolMessageType('Decimal64', (_message.Message,), dict( + DESCRIPTOR = _DECIMAL64, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.Decimal64) + )) +_sym_db.RegisterMessage(Decimal64) + +ScalarArray = _reflection.GeneratedProtocolMessageType('ScalarArray', (_message.Message,), dict( + DESCRIPTOR = _SCALARARRAY, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.ScalarArray) + )) +_sym_db.RegisterMessage(ScalarArray) + +SubscribeRequest = _reflection.GeneratedProtocolMessageType('SubscribeRequest', (_message.Message,), dict( + DESCRIPTOR = _SUBSCRIBEREQUEST, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.SubscribeRequest) + )) +_sym_db.RegisterMessage(SubscribeRequest) + +Poll = _reflection.GeneratedProtocolMessageType('Poll', (_message.Message,), dict( + DESCRIPTOR = _POLL, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.Poll) + )) +_sym_db.RegisterMessage(Poll) + +SubscribeResponse = _reflection.GeneratedProtocolMessageType('SubscribeResponse', (_message.Message,), dict( + DESCRIPTOR = _SUBSCRIBERESPONSE, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.SubscribeResponse) + )) +_sym_db.RegisterMessage(SubscribeResponse) + +SubscriptionList = _reflection.GeneratedProtocolMessageType('SubscriptionList', (_message.Message,), dict( + DESCRIPTOR = _SUBSCRIPTIONLIST, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.SubscriptionList) + )) +_sym_db.RegisterMessage(SubscriptionList) + +Subscription = _reflection.GeneratedProtocolMessageType('Subscription', (_message.Message,), dict( + DESCRIPTOR = _SUBSCRIPTION, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.Subscription) + )) +_sym_db.RegisterMessage(Subscription) + +QOSMarking = _reflection.GeneratedProtocolMessageType('QOSMarking', (_message.Message,), dict( + DESCRIPTOR = _QOSMARKING, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.QOSMarking) + )) +_sym_db.RegisterMessage(QOSMarking) + +Alias = _reflection.GeneratedProtocolMessageType('Alias', (_message.Message,), dict( + DESCRIPTOR = _ALIAS, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.Alias) + )) +_sym_db.RegisterMessage(Alias) + +AliasList = _reflection.GeneratedProtocolMessageType('AliasList', (_message.Message,), dict( + DESCRIPTOR = _ALIASLIST, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.AliasList) + )) +_sym_db.RegisterMessage(AliasList) + +SetRequest = _reflection.GeneratedProtocolMessageType('SetRequest', (_message.Message,), dict( + DESCRIPTOR = _SETREQUEST, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.SetRequest) + )) +_sym_db.RegisterMessage(SetRequest) + +SetResponse = _reflection.GeneratedProtocolMessageType('SetResponse', (_message.Message,), dict( + DESCRIPTOR = _SETRESPONSE, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.SetResponse) + )) +_sym_db.RegisterMessage(SetResponse) + +UpdateResult = _reflection.GeneratedProtocolMessageType('UpdateResult', (_message.Message,), dict( + DESCRIPTOR = _UPDATERESULT, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.UpdateResult) + )) +_sym_db.RegisterMessage(UpdateResult) + +GetRequest = _reflection.GeneratedProtocolMessageType('GetRequest', (_message.Message,), dict( + DESCRIPTOR = _GETREQUEST, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.GetRequest) + )) +_sym_db.RegisterMessage(GetRequest) + +GetResponse = _reflection.GeneratedProtocolMessageType('GetResponse', (_message.Message,), dict( + DESCRIPTOR = _GETRESPONSE, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.GetResponse) + )) +_sym_db.RegisterMessage(GetResponse) + +CapabilityRequest = _reflection.GeneratedProtocolMessageType('CapabilityRequest', (_message.Message,), dict( + DESCRIPTOR = _CAPABILITYREQUEST, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.CapabilityRequest) + )) +_sym_db.RegisterMessage(CapabilityRequest) + +CapabilityResponse = _reflection.GeneratedProtocolMessageType('CapabilityResponse', (_message.Message,), dict( + DESCRIPTOR = _CAPABILITYRESPONSE, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.CapabilityResponse) + )) +_sym_db.RegisterMessage(CapabilityResponse) + +ModelData = _reflection.GeneratedProtocolMessageType('ModelData', (_message.Message,), dict( + DESCRIPTOR = _MODELDATA, + __module__ = 'proto.gnmi.gnmi_pb2' + # @@protoc_insertion_point(class_scope:gnmi.ModelData) + )) +_sym_db.RegisterMessage(ModelData) + +google_dot_protobuf_dot_descriptor__pb2.FileOptions.RegisterExtension(gnmi_service) + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\312>\0050.5.0')) +_UPDATE.fields_by_name['value'].has_options = True +_UPDATE.fields_by_name['value']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001')) +_PATH.fields_by_name['element'].has_options = True +_PATH.fields_by_name['element']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001')) +_PATHELEM_KEYENTRY.has_options = True +_PATHELEM_KEYENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +_VALUE.has_options = True +_VALUE._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('\030\001')) +_ERROR.has_options = True +_ERROR._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('\030\001')) +_SUBSCRIBERESPONSE.fields_by_name['error'].has_options = True +_SUBSCRIBERESPONSE.fields_by_name['error']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001')) +_SETRESPONSE.fields_by_name['message'].has_options = True +_SETRESPONSE.fields_by_name['message']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001')) +_UPDATERESULT.fields_by_name['timestamp'].has_options = True +_UPDATERESULT.fields_by_name['timestamp']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001')) +_UPDATERESULT.fields_by_name['message'].has_options = True +_UPDATERESULT.fields_by_name['message']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001')) +_GETRESPONSE.fields_by_name['error'].has_options = True +_GETRESPONSE.fields_by_name['error']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001')) +try: + # THESE ELEMENTS WILL BE DEPRECATED. + # Please use the generated *_pb2_grpc.py files instead. + import grpc + from grpc.beta import implementations as beta_implementations + from grpc.beta import interfaces as beta_interfaces + from grpc.framework.common import cardinality + from grpc.framework.interfaces.face import utilities as face_utilities + + + class gNMIStub(object): + # missing associated documentation comment in .proto file + pass + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Capabilities = channel.unary_unary( + '/gnmi.gNMI/Capabilities', + request_serializer=CapabilityRequest.SerializeToString, + response_deserializer=CapabilityResponse.FromString, + ) + self.Get = channel.unary_unary( + '/gnmi.gNMI/Get', + request_serializer=GetRequest.SerializeToString, + response_deserializer=GetResponse.FromString, + ) + self.Set = channel.unary_unary( + '/gnmi.gNMI/Set', + request_serializer=SetRequest.SerializeToString, + response_deserializer=SetResponse.FromString, + ) + self.Subscribe = channel.stream_stream( + '/gnmi.gNMI/Subscribe', + request_serializer=SubscribeRequest.SerializeToString, + response_deserializer=SubscribeResponse.FromString, + ) + + + class gNMIServicer(object): + # missing associated documentation comment in .proto file + pass + + def Capabilities(self, request, context): + """Capabilities allows the client to retrieve the set of capabilities that + is supported by the target. This allows the target to validate the + service version that is implemented and retrieve the set of models that + the target supports. The models can then be specified in subsequent RPCs + to restrict the set of data that is utilized. + Reference: gNMI Specification Section 3.2 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Get(self, request, context): + """Retrieve a snapshot of data from the target. A Get RPC requests that the + target snapshots a subset of the data tree as specified by the paths + included in the message and serializes this to be returned to the + client using the specified encoding. + Reference: gNMI Specification Section 3.3 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Set(self, request, context): + """Set allows the client to modify the state of data on the target. The + paths to modified along with the new values that the client wishes + to set the value to. + Reference: gNMI Specification Section 3.4 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Subscribe(self, request_iterator, context): + """Subscribe allows a client to request the target to send it values + of particular paths within the data tree. These values may be streamed + at a particular cadence (STREAM), sent one off on a long-lived channel + (POLL), or sent as a one-off retrieval (ONCE). + Reference: gNMI Specification Section 3.5 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + + def add_gNMIServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Capabilities': grpc.unary_unary_rpc_method_handler( + servicer.Capabilities, + request_deserializer=CapabilityRequest.FromString, + response_serializer=CapabilityResponse.SerializeToString, + ), + 'Get': grpc.unary_unary_rpc_method_handler( + servicer.Get, + request_deserializer=GetRequest.FromString, + response_serializer=GetResponse.SerializeToString, + ), + 'Set': grpc.unary_unary_rpc_method_handler( + servicer.Set, + request_deserializer=SetRequest.FromString, + response_serializer=SetResponse.SerializeToString, + ), + 'Subscribe': grpc.stream_stream_rpc_method_handler( + servicer.Subscribe, + request_deserializer=SubscribeRequest.FromString, + response_serializer=SubscribeResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'gnmi.gNMI', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + class BetagNMIServicer(object): + """The Beta API is deprecated for 0.15.0 and later. + + It is recommended to use the GA API (classes and functions in this + file not marked beta) for all further purposes. This class was generated + only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" + # missing associated documentation comment in .proto file + pass + def Capabilities(self, request, context): + """Capabilities allows the client to retrieve the set of capabilities that + is supported by the target. This allows the target to validate the + service version that is implemented and retrieve the set of models that + the target supports. The models can then be specified in subsequent RPCs + to restrict the set of data that is utilized. + Reference: gNMI Specification Section 3.2 + """ + context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) + def Get(self, request, context): + """Retrieve a snapshot of data from the target. A Get RPC requests that the + target snapshots a subset of the data tree as specified by the paths + included in the message and serializes this to be returned to the + client using the specified encoding. + Reference: gNMI Specification Section 3.3 + """ + context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) + def Set(self, request, context): + """Set allows the client to modify the state of data on the target. The + paths to modified along with the new values that the client wishes + to set the value to. + Reference: gNMI Specification Section 3.4 + """ + context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) + def Subscribe(self, request_iterator, context): + """Subscribe allows a client to request the target to send it values + of particular paths within the data tree. These values may be streamed + at a particular cadence (STREAM), sent one off on a long-lived channel + (POLL), or sent as a one-off retrieval (ONCE). + Reference: gNMI Specification Section 3.5 + """ + context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) + + + class BetagNMIStub(object): + """The Beta API is deprecated for 0.15.0 and later. + + It is recommended to use the GA API (classes and functions in this + file not marked beta) for all further purposes. This class was generated + only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" + # missing associated documentation comment in .proto file + pass + def Capabilities(self, request, timeout, metadata=None, with_call=False, protocol_options=None): + """Capabilities allows the client to retrieve the set of capabilities that + is supported by the target. This allows the target to validate the + service version that is implemented and retrieve the set of models that + the target supports. The models can then be specified in subsequent RPCs + to restrict the set of data that is utilized. + Reference: gNMI Specification Section 3.2 + """ + raise NotImplementedError() + Capabilities.future = None + def Get(self, request, timeout, metadata=None, with_call=False, protocol_options=None): + """Retrieve a snapshot of data from the target. A Get RPC requests that the + target snapshots a subset of the data tree as specified by the paths + included in the message and serializes this to be returned to the + client using the specified encoding. + Reference: gNMI Specification Section 3.3 + """ + raise NotImplementedError() + Get.future = None + def Set(self, request, timeout, metadata=None, with_call=False, protocol_options=None): + """Set allows the client to modify the state of data on the target. The + paths to modified along with the new values that the client wishes + to set the value to. + Reference: gNMI Specification Section 3.4 + """ + raise NotImplementedError() + Set.future = None + def Subscribe(self, request_iterator, timeout, metadata=None, with_call=False, protocol_options=None): + """Subscribe allows a client to request the target to send it values + of particular paths within the data tree. These values may be streamed + at a particular cadence (STREAM), sent one off on a long-lived channel + (POLL), or sent as a one-off retrieval (ONCE). + Reference: gNMI Specification Section 3.5 + """ + raise NotImplementedError() + + + def beta_create_gNMI_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): + """The Beta API is deprecated for 0.15.0 and later. + + It is recommended to use the GA API (classes and functions in this + file not marked beta) for all further purposes. This function was + generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" + request_deserializers = { + ('gnmi.gNMI', 'Capabilities'): CapabilityRequest.FromString, + ('gnmi.gNMI', 'Get'): GetRequest.FromString, + ('gnmi.gNMI', 'Set'): SetRequest.FromString, + ('gnmi.gNMI', 'Subscribe'): SubscribeRequest.FromString, + } + response_serializers = { + ('gnmi.gNMI', 'Capabilities'): CapabilityResponse.SerializeToString, + ('gnmi.gNMI', 'Get'): GetResponse.SerializeToString, + ('gnmi.gNMI', 'Set'): SetResponse.SerializeToString, + ('gnmi.gNMI', 'Subscribe'): SubscribeResponse.SerializeToString, + } + method_implementations = { + ('gnmi.gNMI', 'Capabilities'): face_utilities.unary_unary_inline(servicer.Capabilities), + ('gnmi.gNMI', 'Get'): face_utilities.unary_unary_inline(servicer.Get), + ('gnmi.gNMI', 'Set'): face_utilities.unary_unary_inline(servicer.Set), + ('gnmi.gNMI', 'Subscribe'): face_utilities.stream_stream_inline(servicer.Subscribe), + } + server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) + return beta_implementations.server(method_implementations, options=server_options) + + + def beta_create_gNMI_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): + """The Beta API is deprecated for 0.15.0 and later. + + It is recommended to use the GA API (classes and functions in this + file not marked beta) for all further purposes. This function was + generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" + request_serializers = { + ('gnmi.gNMI', 'Capabilities'): CapabilityRequest.SerializeToString, + ('gnmi.gNMI', 'Get'): GetRequest.SerializeToString, + ('gnmi.gNMI', 'Set'): SetRequest.SerializeToString, + ('gnmi.gNMI', 'Subscribe'): SubscribeRequest.SerializeToString, + } + response_deserializers = { + ('gnmi.gNMI', 'Capabilities'): CapabilityResponse.FromString, + ('gnmi.gNMI', 'Get'): GetResponse.FromString, + ('gnmi.gNMI', 'Set'): SetResponse.FromString, + ('gnmi.gNMI', 'Subscribe'): SubscribeResponse.FromString, + } + cardinalities = { + 'Capabilities': cardinality.Cardinality.UNARY_UNARY, + 'Get': cardinality.Cardinality.UNARY_UNARY, + 'Set': cardinality.Cardinality.UNARY_UNARY, + 'Subscribe': cardinality.Cardinality.STREAM_STREAM, + } + stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) + return beta_implementations.dynamic_stub(channel, 'gnmi.gNMI', cardinalities, options=stub_options) +except ImportError: + pass +# @@protoc_insertion_point(module_scope) diff --git a/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_pb2.pyi b/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_pb2.pyi new file mode 100644 index 0000000000000000000000000000000000000000..423bcfb90e1a603c2ebcdc29092569c7695db2d0 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_pb2.pyi @@ -0,0 +1,380 @@ +from google.protobuf import any_pb2 as _any_pb2 +from google.protobuf import descriptor_pb2 as _descriptor_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +ASCII: Encoding +BYTES: Encoding +DESCRIPTOR: _descriptor.FileDescriptor +EID_EXPERIMENTAL: ExtensionID +EID_UNSET: ExtensionID +GNMI_SERVICE_FIELD_NUMBER: _ClassVar[int] +JSON: Encoding +JSON_IETF: Encoding +ON_CHANGE: SubscriptionMode +PROTO: Encoding +SAMPLE: SubscriptionMode +TARGET_DEFINED: SubscriptionMode +gnmi_service: _descriptor.FieldDescriptor + +class CapabilityRequest(_message.Message): + __slots__ = ["extension"] + EXTENSION_FIELD_NUMBER: _ClassVar[int] + extension: _containers.RepeatedCompositeFieldContainer[Extension] + def __init__(self, extension: _Optional[_Iterable[_Union[Extension, _Mapping]]] = ...) -> None: ... + +class CapabilityResponse(_message.Message): + __slots__ = ["extension", "gNMI_version", "supported_encodings", "supported_models"] + EXTENSION_FIELD_NUMBER: _ClassVar[int] + GNMI_VERSION_FIELD_NUMBER: _ClassVar[int] + SUPPORTED_ENCODINGS_FIELD_NUMBER: _ClassVar[int] + SUPPORTED_MODELS_FIELD_NUMBER: _ClassVar[int] + extension: _containers.RepeatedCompositeFieldContainer[Extension] + gNMI_version: str + supported_encodings: _containers.RepeatedScalarFieldContainer[Encoding] + supported_models: _containers.RepeatedCompositeFieldContainer[ModelData] + def __init__(self, supported_models: _Optional[_Iterable[_Union[ModelData, _Mapping]]] = ..., supported_encodings: _Optional[_Iterable[_Union[Encoding, str]]] = ..., gNMI_version: _Optional[str] = ..., extension: _Optional[_Iterable[_Union[Extension, _Mapping]]] = ...) -> None: ... + +class Decimal64(_message.Message): + __slots__ = ["digits", "precision"] + DIGITS_FIELD_NUMBER: _ClassVar[int] + PRECISION_FIELD_NUMBER: _ClassVar[int] + digits: int + precision: int + def __init__(self, digits: _Optional[int] = ..., precision: _Optional[int] = ...) -> None: ... + +class Error(_message.Message): + __slots__ = ["code", "data", "message"] + CODE_FIELD_NUMBER: _ClassVar[int] + DATA_FIELD_NUMBER: _ClassVar[int] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + code: int + data: _any_pb2.Any + message: str + def __init__(self, code: _Optional[int] = ..., message: _Optional[str] = ..., data: _Optional[_Union[_any_pb2.Any, _Mapping]] = ...) -> None: ... + +class Extension(_message.Message): + __slots__ = ["history", "master_arbitration", "registered_ext"] + HISTORY_FIELD_NUMBER: _ClassVar[int] + MASTER_ARBITRATION_FIELD_NUMBER: _ClassVar[int] + REGISTERED_EXT_FIELD_NUMBER: _ClassVar[int] + history: History + master_arbitration: MasterArbitration + registered_ext: RegisteredExtension + def __init__(self, registered_ext: _Optional[_Union[RegisteredExtension, _Mapping]] = ..., master_arbitration: _Optional[_Union[MasterArbitration, _Mapping]] = ..., history: _Optional[_Union[History, _Mapping]] = ...) -> None: ... + +class GetRequest(_message.Message): + __slots__ = ["encoding", "extension", "path", "prefix", "type", "use_models"] + class DataType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = [] + ALL: GetRequest.DataType + CONFIG: GetRequest.DataType + ENCODING_FIELD_NUMBER: _ClassVar[int] + EXTENSION_FIELD_NUMBER: _ClassVar[int] + OPERATIONAL: GetRequest.DataType + PATH_FIELD_NUMBER: _ClassVar[int] + PREFIX_FIELD_NUMBER: _ClassVar[int] + STATE: GetRequest.DataType + TYPE_FIELD_NUMBER: _ClassVar[int] + USE_MODELS_FIELD_NUMBER: _ClassVar[int] + encoding: Encoding + extension: _containers.RepeatedCompositeFieldContainer[Extension] + path: _containers.RepeatedCompositeFieldContainer[Path] + prefix: Path + type: GetRequest.DataType + use_models: _containers.RepeatedCompositeFieldContainer[ModelData] + def __init__(self, prefix: _Optional[_Union[Path, _Mapping]] = ..., path: _Optional[_Iterable[_Union[Path, _Mapping]]] = ..., type: _Optional[_Union[GetRequest.DataType, str]] = ..., encoding: _Optional[_Union[Encoding, str]] = ..., use_models: _Optional[_Iterable[_Union[ModelData, _Mapping]]] = ..., extension: _Optional[_Iterable[_Union[Extension, _Mapping]]] = ...) -> None: ... + +class GetResponse(_message.Message): + __slots__ = ["error", "extension", "notification"] + ERROR_FIELD_NUMBER: _ClassVar[int] + EXTENSION_FIELD_NUMBER: _ClassVar[int] + NOTIFICATION_FIELD_NUMBER: _ClassVar[int] + error: Error + extension: _containers.RepeatedCompositeFieldContainer[Extension] + notification: _containers.RepeatedCompositeFieldContainer[Notification] + def __init__(self, notification: _Optional[_Iterable[_Union[Notification, _Mapping]]] = ..., error: _Optional[_Union[Error, _Mapping]] = ..., extension: _Optional[_Iterable[_Union[Extension, _Mapping]]] = ...) -> None: ... + +class History(_message.Message): + __slots__ = ["range", "snapshot_time"] + RANGE_FIELD_NUMBER: _ClassVar[int] + SNAPSHOT_TIME_FIELD_NUMBER: _ClassVar[int] + range: TimeRange + snapshot_time: int + def __init__(self, snapshot_time: _Optional[int] = ..., range: _Optional[_Union[TimeRange, _Mapping]] = ...) -> None: ... + +class MasterArbitration(_message.Message): + __slots__ = ["election_id", "role"] + ELECTION_ID_FIELD_NUMBER: _ClassVar[int] + ROLE_FIELD_NUMBER: _ClassVar[int] + election_id: Uint128 + role: Role + def __init__(self, role: _Optional[_Union[Role, _Mapping]] = ..., election_id: _Optional[_Union[Uint128, _Mapping]] = ...) -> None: ... + +class ModelData(_message.Message): + __slots__ = ["name", "organization", "version"] + NAME_FIELD_NUMBER: _ClassVar[int] + ORGANIZATION_FIELD_NUMBER: _ClassVar[int] + VERSION_FIELD_NUMBER: _ClassVar[int] + name: str + organization: str + version: str + def __init__(self, name: _Optional[str] = ..., organization: _Optional[str] = ..., version: _Optional[str] = ...) -> None: ... + +class Notification(_message.Message): + __slots__ = ["atomic", "delete", "prefix", "timestamp", "update"] + ATOMIC_FIELD_NUMBER: _ClassVar[int] + DELETE_FIELD_NUMBER: _ClassVar[int] + PREFIX_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + UPDATE_FIELD_NUMBER: _ClassVar[int] + atomic: bool + delete: _containers.RepeatedCompositeFieldContainer[Path] + prefix: Path + timestamp: int + update: _containers.RepeatedCompositeFieldContainer[Update] + def __init__(self, timestamp: _Optional[int] = ..., prefix: _Optional[_Union[Path, _Mapping]] = ..., update: _Optional[_Iterable[_Union[Update, _Mapping]]] = ..., delete: _Optional[_Iterable[_Union[Path, _Mapping]]] = ..., atomic: bool = ...) -> None: ... + +class Path(_message.Message): + __slots__ = ["elem", "element", "origin", "target"] + ELEMENT_FIELD_NUMBER: _ClassVar[int] + ELEM_FIELD_NUMBER: _ClassVar[int] + ORIGIN_FIELD_NUMBER: _ClassVar[int] + TARGET_FIELD_NUMBER: _ClassVar[int] + elem: _containers.RepeatedCompositeFieldContainer[PathElem] + element: _containers.RepeatedScalarFieldContainer[str] + origin: str + target: str + def __init__(self, element: _Optional[_Iterable[str]] = ..., origin: _Optional[str] = ..., elem: _Optional[_Iterable[_Union[PathElem, _Mapping]]] = ..., target: _Optional[str] = ...) -> None: ... + +class PathElem(_message.Message): + __slots__ = ["key", "name"] + class KeyEntry(_message.Message): + __slots__ = ["key", "value"] + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + KEY_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + key: _containers.ScalarMap[str, str] + name: str + def __init__(self, name: _Optional[str] = ..., key: _Optional[_Mapping[str, str]] = ...) -> None: ... + +class Poll(_message.Message): + __slots__ = [] + def __init__(self) -> None: ... + +class QOSMarking(_message.Message): + __slots__ = ["marking"] + MARKING_FIELD_NUMBER: _ClassVar[int] + marking: int + def __init__(self, marking: _Optional[int] = ...) -> None: ... + +class RegisteredExtension(_message.Message): + __slots__ = ["id", "msg"] + ID_FIELD_NUMBER: _ClassVar[int] + MSG_FIELD_NUMBER: _ClassVar[int] + id: ExtensionID + msg: bytes + def __init__(self, id: _Optional[_Union[ExtensionID, str]] = ..., msg: _Optional[bytes] = ...) -> None: ... + +class Role(_message.Message): + __slots__ = ["id"] + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class ScalarArray(_message.Message): + __slots__ = ["element"] + ELEMENT_FIELD_NUMBER: _ClassVar[int] + element: _containers.RepeatedCompositeFieldContainer[TypedValue] + def __init__(self, element: _Optional[_Iterable[_Union[TypedValue, _Mapping]]] = ...) -> None: ... + +class SetRequest(_message.Message): + __slots__ = ["delete", "extension", "prefix", "replace", "update"] + DELETE_FIELD_NUMBER: _ClassVar[int] + EXTENSION_FIELD_NUMBER: _ClassVar[int] + PREFIX_FIELD_NUMBER: _ClassVar[int] + REPLACE_FIELD_NUMBER: _ClassVar[int] + UPDATE_FIELD_NUMBER: _ClassVar[int] + delete: _containers.RepeatedCompositeFieldContainer[Path] + extension: _containers.RepeatedCompositeFieldContainer[Extension] + prefix: Path + replace: _containers.RepeatedCompositeFieldContainer[Update] + update: _containers.RepeatedCompositeFieldContainer[Update] + def __init__(self, prefix: _Optional[_Union[Path, _Mapping]] = ..., delete: _Optional[_Iterable[_Union[Path, _Mapping]]] = ..., replace: _Optional[_Iterable[_Union[Update, _Mapping]]] = ..., update: _Optional[_Iterable[_Union[Update, _Mapping]]] = ..., extension: _Optional[_Iterable[_Union[Extension, _Mapping]]] = ...) -> None: ... + +class SetResponse(_message.Message): + __slots__ = ["extension", "message", "prefix", "response", "timestamp"] + EXTENSION_FIELD_NUMBER: _ClassVar[int] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + PREFIX_FIELD_NUMBER: _ClassVar[int] + RESPONSE_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + extension: _containers.RepeatedCompositeFieldContainer[Extension] + message: Error + prefix: Path + response: _containers.RepeatedCompositeFieldContainer[UpdateResult] + timestamp: int + def __init__(self, prefix: _Optional[_Union[Path, _Mapping]] = ..., response: _Optional[_Iterable[_Union[UpdateResult, _Mapping]]] = ..., message: _Optional[_Union[Error, _Mapping]] = ..., timestamp: _Optional[int] = ..., extension: _Optional[_Iterable[_Union[Extension, _Mapping]]] = ...) -> None: ... + +class SubscribeRequest(_message.Message): + __slots__ = ["extension", "poll", "subscribe"] + EXTENSION_FIELD_NUMBER: _ClassVar[int] + POLL_FIELD_NUMBER: _ClassVar[int] + SUBSCRIBE_FIELD_NUMBER: _ClassVar[int] + extension: _containers.RepeatedCompositeFieldContainer[Extension] + poll: Poll + subscribe: SubscriptionList + def __init__(self, subscribe: _Optional[_Union[SubscriptionList, _Mapping]] = ..., poll: _Optional[_Union[Poll, _Mapping]] = ..., extension: _Optional[_Iterable[_Union[Extension, _Mapping]]] = ...) -> None: ... + +class SubscribeResponse(_message.Message): + __slots__ = ["error", "extension", "sync_response", "update"] + ERROR_FIELD_NUMBER: _ClassVar[int] + EXTENSION_FIELD_NUMBER: _ClassVar[int] + SYNC_RESPONSE_FIELD_NUMBER: _ClassVar[int] + UPDATE_FIELD_NUMBER: _ClassVar[int] + error: Error + extension: _containers.RepeatedCompositeFieldContainer[Extension] + sync_response: bool + update: Notification + def __init__(self, update: _Optional[_Union[Notification, _Mapping]] = ..., sync_response: bool = ..., error: _Optional[_Union[Error, _Mapping]] = ..., extension: _Optional[_Iterable[_Union[Extension, _Mapping]]] = ...) -> None: ... + +class Subscription(_message.Message): + __slots__ = ["heartbeat_interval", "mode", "path", "sample_interval", "suppress_redundant"] + HEARTBEAT_INTERVAL_FIELD_NUMBER: _ClassVar[int] + MODE_FIELD_NUMBER: _ClassVar[int] + PATH_FIELD_NUMBER: _ClassVar[int] + SAMPLE_INTERVAL_FIELD_NUMBER: _ClassVar[int] + SUPPRESS_REDUNDANT_FIELD_NUMBER: _ClassVar[int] + heartbeat_interval: int + mode: SubscriptionMode + path: Path + sample_interval: int + suppress_redundant: bool + def __init__(self, path: _Optional[_Union[Path, _Mapping]] = ..., mode: _Optional[_Union[SubscriptionMode, str]] = ..., sample_interval: _Optional[int] = ..., suppress_redundant: bool = ..., heartbeat_interval: _Optional[int] = ...) -> None: ... + +class SubscriptionList(_message.Message): + __slots__ = ["allow_aggregation", "encoding", "mode", "prefix", "qos", "subscription", "updates_only", "use_models"] + class Mode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = [] + ALLOW_AGGREGATION_FIELD_NUMBER: _ClassVar[int] + ENCODING_FIELD_NUMBER: _ClassVar[int] + MODE_FIELD_NUMBER: _ClassVar[int] + ONCE: SubscriptionList.Mode + POLL: SubscriptionList.Mode + PREFIX_FIELD_NUMBER: _ClassVar[int] + QOS_FIELD_NUMBER: _ClassVar[int] + STREAM: SubscriptionList.Mode + SUBSCRIPTION_FIELD_NUMBER: _ClassVar[int] + UPDATES_ONLY_FIELD_NUMBER: _ClassVar[int] + USE_MODELS_FIELD_NUMBER: _ClassVar[int] + allow_aggregation: bool + encoding: Encoding + mode: SubscriptionList.Mode + prefix: Path + qos: QOSMarking + subscription: _containers.RepeatedCompositeFieldContainer[Subscription] + updates_only: bool + use_models: _containers.RepeatedCompositeFieldContainer[ModelData] + def __init__(self, prefix: _Optional[_Union[Path, _Mapping]] = ..., subscription: _Optional[_Iterable[_Union[Subscription, _Mapping]]] = ..., qos: _Optional[_Union[QOSMarking, _Mapping]] = ..., mode: _Optional[_Union[SubscriptionList.Mode, str]] = ..., allow_aggregation: bool = ..., use_models: _Optional[_Iterable[_Union[ModelData, _Mapping]]] = ..., encoding: _Optional[_Union[Encoding, str]] = ..., updates_only: bool = ...) -> None: ... + +class TimeRange(_message.Message): + __slots__ = ["end", "start"] + END_FIELD_NUMBER: _ClassVar[int] + START_FIELD_NUMBER: _ClassVar[int] + end: int + start: int + def __init__(self, start: _Optional[int] = ..., end: _Optional[int] = ...) -> None: ... + +class TypedValue(_message.Message): + __slots__ = ["any_val", "ascii_val", "bool_val", "bytes_val", "decimal_val", "double_val", "float_val", "int_val", "json_ietf_val", "json_val", "leaflist_val", "proto_bytes", "string_val", "uint_val"] + ANY_VAL_FIELD_NUMBER: _ClassVar[int] + ASCII_VAL_FIELD_NUMBER: _ClassVar[int] + BOOL_VAL_FIELD_NUMBER: _ClassVar[int] + BYTES_VAL_FIELD_NUMBER: _ClassVar[int] + DECIMAL_VAL_FIELD_NUMBER: _ClassVar[int] + DOUBLE_VAL_FIELD_NUMBER: _ClassVar[int] + FLOAT_VAL_FIELD_NUMBER: _ClassVar[int] + INT_VAL_FIELD_NUMBER: _ClassVar[int] + JSON_IETF_VAL_FIELD_NUMBER: _ClassVar[int] + JSON_VAL_FIELD_NUMBER: _ClassVar[int] + LEAFLIST_VAL_FIELD_NUMBER: _ClassVar[int] + PROTO_BYTES_FIELD_NUMBER: _ClassVar[int] + STRING_VAL_FIELD_NUMBER: _ClassVar[int] + UINT_VAL_FIELD_NUMBER: _ClassVar[int] + any_val: _any_pb2.Any + ascii_val: str + bool_val: bool + bytes_val: bytes + decimal_val: Decimal64 + double_val: float + float_val: float + int_val: int + json_ietf_val: bytes + json_val: bytes + leaflist_val: ScalarArray + proto_bytes: bytes + string_val: str + uint_val: int + def __init__(self, string_val: _Optional[str] = ..., int_val: _Optional[int] = ..., uint_val: _Optional[int] = ..., bool_val: bool = ..., bytes_val: _Optional[bytes] = ..., float_val: _Optional[float] = ..., double_val: _Optional[float] = ..., decimal_val: _Optional[_Union[Decimal64, _Mapping]] = ..., leaflist_val: _Optional[_Union[ScalarArray, _Mapping]] = ..., any_val: _Optional[_Union[_any_pb2.Any, _Mapping]] = ..., json_val: _Optional[bytes] = ..., json_ietf_val: _Optional[bytes] = ..., ascii_val: _Optional[str] = ..., proto_bytes: _Optional[bytes] = ...) -> None: ... + +class Uint128(_message.Message): + __slots__ = ["high", "low"] + HIGH_FIELD_NUMBER: _ClassVar[int] + LOW_FIELD_NUMBER: _ClassVar[int] + high: int + low: int + def __init__(self, high: _Optional[int] = ..., low: _Optional[int] = ...) -> None: ... + +class Update(_message.Message): + __slots__ = ["duplicates", "path", "val", "value"] + DUPLICATES_FIELD_NUMBER: _ClassVar[int] + PATH_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + VAL_FIELD_NUMBER: _ClassVar[int] + duplicates: int + path: Path + val: TypedValue + value: Value + def __init__(self, path: _Optional[_Union[Path, _Mapping]] = ..., value: _Optional[_Union[Value, _Mapping]] = ..., val: _Optional[_Union[TypedValue, _Mapping]] = ..., duplicates: _Optional[int] = ...) -> None: ... + +class UpdateResult(_message.Message): + __slots__ = ["message", "op", "path", "timestamp"] + class Operation(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = [] + DELETE: UpdateResult.Operation + INVALID: UpdateResult.Operation + MESSAGE_FIELD_NUMBER: _ClassVar[int] + OP_FIELD_NUMBER: _ClassVar[int] + PATH_FIELD_NUMBER: _ClassVar[int] + REPLACE: UpdateResult.Operation + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + UPDATE: UpdateResult.Operation + message: Error + op: UpdateResult.Operation + path: Path + timestamp: int + def __init__(self, timestamp: _Optional[int] = ..., path: _Optional[_Union[Path, _Mapping]] = ..., message: _Optional[_Union[Error, _Mapping]] = ..., op: _Optional[_Union[UpdateResult.Operation, str]] = ...) -> None: ... + +class Value(_message.Message): + __slots__ = ["type", "value"] + TYPE_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + type: Encoding + value: bytes + def __init__(self, value: _Optional[bytes] = ..., type: _Optional[_Union[Encoding, str]] = ...) -> None: ... + +class ExtensionID(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = [] + +class Encoding(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = [] + +class SubscriptionMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = [] \ No newline at end of file diff --git a/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_pb2_grpc.py b/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_pb2_grpc.py new file mode 100644 index 0000000000000000000000000000000000000000..517d3d9eb41c5833a28cc9b7f43859dde186f348 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/gnmi/gnmi_pb2_grpc.py @@ -0,0 +1,185 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from . import gnmi_pb2 as gnmi__pb2 + + +class gNMIStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Capabilities = channel.unary_unary( + '/gnmi.gNMI/Capabilities', + request_serializer=gnmi__pb2.CapabilityRequest.SerializeToString, + response_deserializer=gnmi__pb2.CapabilityResponse.FromString, + ) + self.Get = channel.unary_unary( + '/gnmi.gNMI/Get', + request_serializer=gnmi__pb2.GetRequest.SerializeToString, + response_deserializer=gnmi__pb2.GetResponse.FromString, + ) + self.Set = channel.unary_unary( + '/gnmi.gNMI/Set', + request_serializer=gnmi__pb2.SetRequest.SerializeToString, + response_deserializer=gnmi__pb2.SetResponse.FromString, + ) + self.Subscribe = channel.stream_stream( + '/gnmi.gNMI/Subscribe', + request_serializer=gnmi__pb2.SubscribeRequest.SerializeToString, + response_deserializer=gnmi__pb2.SubscribeResponse.FromString, + ) + + +class gNMIServicer(object): + """Missing associated documentation comment in .proto file.""" + + def Capabilities(self, request, context): + """Capabilities allows the client to retrieve the set of capabilities that + is supported by the target. This allows the target to validate the + service version that is implemented and retrieve the set of models that + the target supports. The models can then be specified in subsequent RPCs + to restrict the set of data that is utilized. + Reference: gNMI Specification Section 3.2 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Get(self, request, context): + """Retrieve a snapshot of data from the target. A Get RPC requests that the + target snapshots a subset of the data tree as specified by the paths + included in the message and serializes this to be returned to the + client using the specified encoding. + Reference: gNMI Specification Section 3.3 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Set(self, request, context): + """Set allows the client to modify the state of data on the target. The + paths to modified along with the new values that the client wishes + to set the value to. + Reference: gNMI Specification Section 3.4 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Subscribe(self, request_iterator, context): + """Subscribe allows a client to request the target to send it values + of particular paths within the data tree. These values may be streamed + at a particular cadence (STREAM), sent one off on a long-lived channel + (POLL), or sent as a one-off retrieval (ONCE). + Reference: gNMI Specification Section 3.5 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_gNMIServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Capabilities': grpc.unary_unary_rpc_method_handler( + servicer.Capabilities, + request_deserializer=gnmi__pb2.CapabilityRequest.FromString, + response_serializer=gnmi__pb2.CapabilityResponse.SerializeToString, + ), + 'Get': grpc.unary_unary_rpc_method_handler( + servicer.Get, + request_deserializer=gnmi__pb2.GetRequest.FromString, + response_serializer=gnmi__pb2.GetResponse.SerializeToString, + ), + 'Set': grpc.unary_unary_rpc_method_handler( + servicer.Set, + request_deserializer=gnmi__pb2.SetRequest.FromString, + response_serializer=gnmi__pb2.SetResponse.SerializeToString, + ), + 'Subscribe': grpc.stream_stream_rpc_method_handler( + servicer.Subscribe, + request_deserializer=gnmi__pb2.SubscribeRequest.FromString, + response_serializer=gnmi__pb2.SubscribeResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'gnmi.gNMI', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class gNMI(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def Capabilities(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/gnmi.gNMI/Capabilities', + gnmi__pb2.CapabilityRequest.SerializeToString, + gnmi__pb2.CapabilityResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def Get(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/gnmi.gNMI/Get', + gnmi__pb2.GetRequest.SerializeToString, + gnmi__pb2.GetResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def Set(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/gnmi.gNMI/Set', + gnmi__pb2.SetRequest.SerializeToString, + gnmi__pb2.SetResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def Subscribe(request_iterator, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.stream_stream(request_iterator, target, '/gnmi.gNMI/Subscribe', + gnmi__pb2.SubscribeRequest.SerializeToString, + gnmi__pb2.SubscribeResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/Component.py b/src/device/service/drivers/gnmi_openconfig/handlers/Component.py new file mode 100644 index 0000000000000000000000000000000000000000..0b3c1f9705353548025cb4365ea31e68978c79f1 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/Component.py @@ -0,0 +1,63 @@ +# 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. + +import logging +from typing import Any, Dict, List, Tuple +from common.proto.kpi_sample_types_pb2 import KpiSampleType +from ._Handler import _Handler + +LOGGER = logging.getLogger(__name__) + +PATH_IF_CTR = "/interfaces/interface[name={:s}]/state/counters/{:s}" + +class ComponentHandler(_Handler): + def get_resource_key(self) -> str: return '/endpoints/endpoint' + def get_path(self) -> str: return '/components/component' + + def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: + #LOGGER.info('json_data = {:s}'.format(json.dumps(json_data))) + json_component_list : List[Dict] = json_data.get('component', []) + response = [] + for json_component in json_component_list: + #LOGGER.info('json_component = {:s}'.format(json.dumps(json_component))) + + endpoint = {} + + component_type = json_component.get('state', {}).get('type') + if component_type is None: continue + component_type = component_type.replace('oc-platform-types:', '') + component_type = component_type.replace('openconfig-platform-types:', '') + if component_type not in {'PORT'}: continue + endpoint['type'] = '-' + + #LOGGER.info('PORT json_component = {:s}'.format(json.dumps(json_component))) + + component_name = json_component.get('name') + if component_name is None: continue + + # TODO: improve mapping between interface name and component name + # By now, computed by time for the sake of saving time for the Hackfest. + interface_name = component_name.lower().replace('-port', '') + + endpoint['uuid'] = interface_name + endpoint['sample_types'] = { + KpiSampleType.KPISAMPLETYPE_BYTES_RECEIVED : PATH_IF_CTR.format(interface_name, 'in-octets' ), + KpiSampleType.KPISAMPLETYPE_BYTES_TRANSMITTED : PATH_IF_CTR.format(interface_name, 'out-octets'), + KpiSampleType.KPISAMPLETYPE_PACKETS_RECEIVED : PATH_IF_CTR.format(interface_name, 'in-pkts' ), + KpiSampleType.KPISAMPLETYPE_PACKETS_TRANSMITTED: PATH_IF_CTR.format(interface_name, 'out-pkts' ), + } + + if len(endpoint) == 0: continue + response.append(('/endpoints/endpoint[{:s}]'.format(endpoint['uuid']), endpoint)) + return response diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/Interface.py b/src/device/service/drivers/gnmi_openconfig/handlers/Interface.py new file mode 100644 index 0000000000000000000000000000000000000000..20f79b3c2e15b58ab99166a68422fd35d40fd00f --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/Interface.py @@ -0,0 +1,248 @@ +# 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. + +import json, logging +from typing import Any, Dict, List, Tuple +from ._Handler import _Handler +from .Tools import dict_get_first + +LOGGER = logging.getLogger(__name__) + +class InterfaceHandler(_Handler): + def get_resource_key(self) -> str: return '/interface' + def get_path(self) -> str: return '/interfaces/interface' + + def compose(self, resource_key : str, resource_value : Dict, delete : bool = False) -> Tuple[str, str]: + if_name = str (resource_value['name' ]) # ethernet-1/1 + sif_index = int (resource_value.get('sub_if_index' , 0 )) # 0 + + if delete: + PATH_TMPL = '/interfaces/interface[name={:s}]/subinterfaces/subinterface[index={:d}]' + str_path = PATH_TMPL.format(if_name, sif_index) + str_data = json.dumps({}) + return str_path, str_data + + if_enabled = bool(resource_value.get('enabled' , True)) # True/False + sif_enabled = bool(resource_value.get('sub_if_enabled' , True)) # True/False + sif_ipv4_enabled = bool(resource_value.get('sub_if_ipv4_enabled', True)) # True/False + sif_ipv4_address = str (resource_value['sub_if_ipv4_address' ]) # 172.16.0.1 + sif_ipv4_prefix = int (resource_value['sub_if_ipv4_prefix' ]) # 24 + + str_path = '/interfaces/interface[name={:s}]'.format(if_name) + str_data = json.dumps({ + 'name': if_name, + 'config': {'name': if_name, 'enabled': if_enabled}, + 'subinterfaces': { + 'subinterface': { + 'index': sif_index, + 'config': {'index': sif_index, 'enabled': sif_enabled}, + 'ipv4': { + 'config': {'enabled': sif_ipv4_enabled}, + 'addresses': { + 'address': { + 'ip': sif_ipv4_address, + 'config': {'ip': sif_ipv4_address, 'prefix_length': sif_ipv4_prefix}, + } + } + } + } + } + }) + return str_path, str_data + + def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: + #LOGGER.info('json_data = {:s}'.format(json.dumps(json_data))) + json_interface_list : List[Dict] = json_data.get('interface', []) + + response = [] + for json_interface in json_interface_list: + #LOGGER.info('json_interface = {:s}'.format(json.dumps(json_interface))) + + interface = {} + + interface_name = json_interface.get('name') + if interface_name is None: + LOGGER.info('DISCARDED json_interface = {:s}'.format(json.dumps(json_interface))) + continue + interface['name'] = interface_name + + CONFIG_FIELDS = ('config', 'openconfig-interface:config', 'oci:config') + json_config : Dict = dict_get_first(json_interface, CONFIG_FIELDS, default={}) + + STATE_FIELDS = ('state', 'openconfig-interface:state', 'oci:state') + json_state : Dict = dict_get_first(json_interface, STATE_FIELDS, default={}) + + interface_type = json_config.get('type') + if interface_type is None: interface_type = json_state.get('type') + if interface_type is None: + LOGGER.info('DISCARDED json_interface = {:s}'.format(json.dumps(json_interface))) + continue + interface_type = interface_type.replace('ianaift:', '') + interface_type = interface_type.replace('iana-if-type:', '') + interface['type'] = interface_type + + interface_mtu = json_config.get('mtu') + if interface_mtu is None: interface_mtu = json_state.get('mtu') + if interface_mtu is not None: interface['mtu'] = int(interface_mtu) + + interface_enabled = json_config.get('enabled') + if interface_enabled is None: interface_enabled = json_state.get('enabled') + interface['enabled'] = False if interface_enabled is None else bool(interface_enabled) + + interface_management = json_config.get('management') + if interface_management is None: interface_management = json_state.get('management') + interface['management'] = False if interface_management is None else bool(interface_management) + + interface_descr = json_interface.get('config', {}).get('description') + if interface_descr is not None: interface['description'] = interface_descr + + json_subinterfaces = json_interface.get('subinterfaces', {}) + json_subinterface_list : List[Dict] = json_subinterfaces.get('subinterface', []) + + for json_subinterface in json_subinterface_list: + #LOGGER.info('json_subinterface = {:s}'.format(json.dumps(json_subinterface))) + + subinterface = {} + + subinterface_index = json_subinterface.get('state', {}).get('index') + if subinterface_index is None: continue + subinterface['index'] = int(subinterface_index) + + subinterface_name = json_subinterface.get('state', {}).get('name') + if subinterface_name is None: continue + subinterface['name'] = subinterface_name + + subinterface_enabled = json_subinterface.get('state', {}).get('enabled', False) + subinterface['enabled'] = bool(subinterface_enabled) + + VLAN_FIELDS = ('vlan', 'openconfig-vlan:vlan', 'ocv:vlan') + json_vlan = dict_get_first(json_subinterface, VLAN_FIELDS, default={}) + + MATCH_FIELDS = ('match', 'openconfig-vlan:match', 'ocv:match') + json_vlan = dict_get_first(json_vlan, MATCH_FIELDS, default={}) + + SIN_TAG_FIELDS = ('single-tagged', 'openconfig-vlan:single-tagged', 'ocv:single-tagged') + json_vlan = dict_get_first(json_vlan, SIN_TAG_FIELDS, default={}) + + CONFIG_FIELDS = ('config', 'openconfig-vlan:config', 'ocv:config') + json_vlan = dict_get_first(json_vlan, CONFIG_FIELDS, default={}) + + VLAN_ID_FIELDS = ('vlan-id', 'openconfig-vlan:vlan-id', 'ocv:vlan-id') + subinterface_vlan_id = dict_get_first(json_vlan, VLAN_ID_FIELDS) + if subinterface_vlan_id is not None: subinterface['vlan_id'] = subinterface_vlan_id + + + # TODO: implement support for multiple IP addresses per subinterface + + IPV4_FIELDS = ('ipv4', 'openconfig-if-ip:ipv4', 'ociip:ipv4') + json_ipv4 = dict_get_first(json_subinterface, IPV4_FIELDS, default={}) + + IPV4_ADDRESSES_FIELDS = ('addresses', 'openconfig-if-ip:addresses', 'ociip:addresses') + json_ipv4_addresses = dict_get_first(json_ipv4, IPV4_ADDRESSES_FIELDS, default={}) + + IPV4_ADDRESS_FIELDS = ('address', 'openconfig-if-ip:address', 'ociip:address') + json_ipv4_address_list : List[Dict] = dict_get_first(json_ipv4_addresses, IPV4_ADDRESS_FIELDS, default=[]) + + #ipv4_addresses = [] + for json_ipv4_address in json_ipv4_address_list: + #LOGGER.info('json_ipv4_address = {:s}'.format(json.dumps(json_ipv4_address))) + + STATE_FIELDS = ('state', 'openconfig-if-ip:state', 'ociip:state') + json_ipv4_address_state = dict_get_first(json_ipv4_address, STATE_FIELDS, default={}) + + #ipv4_address = {} + + #ORIGIN_FIELDS = ('origin', 'openconfig-if-ip:origin', 'ociip:origin') + #ipv4_address_origin = dict_get_first(json_ipv4_address_state, ORIGIN_FIELDS, default={}) + #if ipv4_address_origin is not None: ipv4_address['origin'] = ipv4_address_origin + + IP_FIELDS = ('ip', 'openconfig-if-ip:ip', 'ociip:ip') + ipv4_address_ip = dict_get_first(json_ipv4_address_state, IP_FIELDS) + #if ipv4_address_ip is not None: ipv4_address['address_ip'] = ipv4_address_ip + if ipv4_address_ip is not None: subinterface['address_ip'] = ipv4_address_ip + + PREFIX_FIELDS = ('prefix-length', 'openconfig-if-ip:prefix-length', 'ociip:prefix-length') + ipv4_address_prefix = dict_get_first(json_ipv4_address_state, PREFIX_FIELDS) + #if ipv4_address_prefix is not None: ipv4_address['address_prefix'] = int(ipv4_address_prefix) + if ipv4_address_prefix is not None: subinterface['address_prefix'] = int(ipv4_address_prefix) + + #if len(ipv4_address) == 0: continue + #ipv4_addresses.append(ipv4_address) + + #subinterface['ipv4_addresses'] = ipv4_addresses + + if len(subinterface) == 0: continue + resource_key = '/interface[{:s}]/subinterface[{:s}]'.format(interface['name'], str(subinterface['index'])) + response.append((resource_key, subinterface)) + + if len(interface) == 0: continue + response.append(('/interface[{:s}]'.format(interface['name']), interface)) + + return response + + def parse_counters(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: + LOGGER.info('[parse_counters] json_data = {:s}'.format(json.dumps(json_data))) + json_interface_list : List[Dict] = json_data.get('interface', []) + + response = [] + for json_interface in json_interface_list: + LOGGER.info('[parse_counters] json_interface = {:s}'.format(json.dumps(json_interface))) + + interface = {} + + NAME_FIELDS = ('name', 'openconfig-interface:name', 'oci:name') + interface_name = dict_get_first(json_interface, NAME_FIELDS) + if interface_name is None: continue + interface['name'] = interface_name + + STATE_FIELDS = ('state', 'openconfig-interface:state', 'oci:state') + json_state = dict_get_first(json_interface, STATE_FIELDS, default={}) + + COUNTERS_FIELDS = ('counters', 'openconfig-interface:counters', 'oci:counters') + json_counters = dict_get_first(json_state, COUNTERS_FIELDS, default={}) + + IN_PKTS_FIELDS = ('in-pkts', 'openconfig-interface:in-pkts', 'oci:in-pkts') + interface_in_pkts = dict_get_first(json_counters, IN_PKTS_FIELDS) + if interface_in_pkts is not None: interface['in-pkts'] = int(interface_in_pkts) + + IN_OCTETS_FIELDS = ('in-octets', 'openconfig-interface:in-octets', 'oci:in-octets') + interface_in_octets = dict_get_first(json_counters, IN_OCTETS_FIELDS) + if interface_in_octets is not None: interface['in-octets'] = int(interface_in_octets) + + IN_ERRORS_FIELDS = ('in-errors', 'openconfig-interface:in-errors', 'oci:in-errors') + interface_in_errors = dict_get_first(json_counters, IN_ERRORS_FIELDS) + if interface_in_errors is not None: interface['in-errors'] = int(interface_in_errors) + + OUT_OCTETS_FIELDS = ('out-octets', 'openconfig-interface:out-octets', 'oci:out-octets') + interface_out_octets = dict_get_first(json_counters, OUT_OCTETS_FIELDS) + if interface_out_octets is not None: interface['out-octets'] = int(interface_out_octets) + + OUT_PKTS_FIELDS = ('out-pkts', 'openconfig-interface:out-pkts', 'oci:out-pkts') + interface_out_pkts = dict_get_first(json_counters, OUT_PKTS_FIELDS) + if interface_out_pkts is not None: interface['out-pkts'] = int(interface_out_pkts) + + OUT_ERRORS_FIELDS = ('out-errors', 'openconfig-interface:out-errors', 'oci:out-errors') + interface_out_errors = dict_get_first(json_counters, OUT_ERRORS_FIELDS) + if interface_out_errors is not None: interface['out-errors'] = int(interface_out_errors) + + OUT_DISCARDS_FIELDS = ('out-discards', 'openconfig-interface:out-discards', 'oci:out-discards') + interface_out_discards = dict_get_first(json_counters, OUT_DISCARDS_FIELDS) + if interface_out_discards is not None: interface['out-discards'] = int(interface_out_discards) + + #LOGGER.info('[parse_counters] interface = {:s}'.format(str(interface))) + + if len(interface) == 0: continue + response.append(('/interface[{:s}]'.format(interface['name']), interface)) + + return response diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/InterfaceCounter.py b/src/device/service/drivers/gnmi_openconfig/handlers/InterfaceCounter.py new file mode 100644 index 0000000000000000000000000000000000000000..a45dc9e7f972445691143df15d6d56d079384fc4 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/InterfaceCounter.py @@ -0,0 +1,80 @@ +# 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. + +import json, logging +from typing import Any, Dict, List, Tuple +from ._Handler import _Handler +from .Tools import dict_get_first + +LOGGER = logging.getLogger(__name__) + +class InterfaceCounterHandler(_Handler): + def get_resource_key(self) -> str: return '/interface/counters' + def get_path(self) -> str: return '/interfaces/interface/state/counters' + + def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: + LOGGER.info('[parse] json_data = {:s}'.format(json.dumps(json_data))) + json_interface_list : List[Dict] = json_data.get('interface', []) + + response = [] + for json_interface in json_interface_list: + LOGGER.info('[parse] json_interface = {:s}'.format(json.dumps(json_interface))) + + interface = {} + + NAME_FIELDS = ('name', 'openconfig-interface:name', 'oci:name') + interface_name = dict_get_first(json_interface, NAME_FIELDS) + if interface_name is None: continue + interface['name'] = interface_name + + STATE_FIELDS = ('state', 'openconfig-interface:state', 'oci:state') + json_state = dict_get_first(json_interface, STATE_FIELDS, default={}) + + COUNTERS_FIELDS = ('counters', 'openconfig-interface:counters', 'oci:counters') + json_counters = dict_get_first(json_state, COUNTERS_FIELDS, default={}) + + IN_PKTS_FIELDS = ('in-pkts', 'openconfig-interface:in-pkts', 'oci:in-pkts') + interface_in_pkts = dict_get_first(json_counters, IN_PKTS_FIELDS) + if interface_in_pkts is not None: interface['in-pkts'] = int(interface_in_pkts) + + IN_OCTETS_FIELDS = ('in-octets', 'openconfig-interface:in-octets', 'oci:in-octets') + interface_in_octets = dict_get_first(json_counters, IN_OCTETS_FIELDS) + if interface_in_octets is not None: interface['in-octets'] = int(interface_in_octets) + + IN_ERRORS_FIELDS = ('in-errors', 'openconfig-interface:in-errors', 'oci:in-errors') + interface_in_errors = dict_get_first(json_counters, IN_ERRORS_FIELDS) + if interface_in_errors is not None: interface['in-errors'] = int(interface_in_errors) + + OUT_OCTETS_FIELDS = ('out-octets', 'openconfig-interface:out-octets', 'oci:out-octets') + interface_out_octets = dict_get_first(json_counters, OUT_OCTETS_FIELDS) + if interface_out_octets is not None: interface['out-octets'] = int(interface_out_octets) + + OUT_PKTS_FIELDS = ('out-pkts', 'openconfig-interface:out-pkts', 'oci:out-pkts') + interface_out_pkts = dict_get_first(json_counters, OUT_PKTS_FIELDS) + if interface_out_pkts is not None: interface['out-pkts'] = int(interface_out_pkts) + + OUT_ERRORS_FIELDS = ('out-errors', 'openconfig-interface:out-errors', 'oci:out-errors') + interface_out_errors = dict_get_first(json_counters, OUT_ERRORS_FIELDS) + if interface_out_errors is not None: interface['out-errors'] = int(interface_out_errors) + + OUT_DISCARDS_FIELDS = ('out-discards', 'openconfig-interface:out-discards', 'oci:out-discards') + interface_out_discards = dict_get_first(json_counters, OUT_DISCARDS_FIELDS) + if interface_out_discards is not None: interface['out-discards'] = int(interface_out_discards) + + #LOGGER.info('[parse] interface = {:s}'.format(str(interface))) + + if len(interface) == 0: continue + response.append(('/interface[{:s}]'.format(interface['name']), interface)) + + return response diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstance.py b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstance.py new file mode 100644 index 0000000000000000000000000000000000000000..aed821a06fa7fcafe96a21ad5f5fa06be2902038 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstance.py @@ -0,0 +1,62 @@ +# 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. + +import json, logging +from typing import Any, Dict, List, Tuple +from ._Handler import _Handler + +LOGGER = logging.getLogger(__name__) + +class NetworkInstanceHandler(_Handler): + def get_resource_key(self) -> str: return '/network_instance' + def get_path(self) -> str: return '/network-instances/network-instance' + + def compose(self, resource_key : str, resource_value : Dict, delete : bool = False) -> Tuple[str, str]: + ni_name = str(resource_value['name']) # test-svc + + if delete: + PATH_TMPL = '/network-instances/network-instance[name={:s}]' + str_path = PATH_TMPL.format(ni_name) + str_data = json.dumps({}) + return str_path, str_data + + ni_type = str(resource_value['type']) # L3VRF / L2VSI / ... + + # not works: [FailedPrecondition] unsupported identifier 'DIRECTLY_CONNECTED' + #protocols = [self._compose_directly_connected()] + + MAP_OC_NI_TYPE = { + 'L3VRF': 'openconfig-network-instance-types:L3VRF', + } + ni_type = MAP_OC_NI_TYPE.get(ni_type, ni_type) + + str_path = '/network-instances/network-instance[name={:s}]'.format(ni_name) + str_data = json.dumps({ + 'name': ni_name, + 'config': {'name': ni_name, 'type': ni_type}, + #'protocols': {'protocol': protocols}, + }) + return str_path, str_data + + def _compose_directly_connected(self, name=None, enabled=True) -> Dict: + identifier = 'DIRECTLY_CONNECTED' + if name is None: name = 'DIRECTLY_CONNECTED' + return { + 'identifier': identifier, 'name': name, + 'config': {'identifier': identifier, 'name': name, 'enabled': enabled}, + } + + def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: + response = [] + return response diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceInterface.py b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceInterface.py new file mode 100644 index 0000000000000000000000000000000000000000..205373fca870ea7338a3c9c043c60306b535c1c0 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceInterface.py @@ -0,0 +1,46 @@ +# 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. + +import json, logging +from typing import Any, Dict, List, Tuple +from ._Handler import _Handler + +LOGGER = logging.getLogger(__name__) + +class NetworkInstanceInterfaceHandler(_Handler): + def get_resource_key(self) -> str: return '/network_instance/interface' + def get_path(self) -> str: return '/network-instances/network-instance/interfaces' + + def compose(self, resource_key : str, resource_value : Dict, delete : bool = False) -> Tuple[str, str]: + ni_name = str(resource_value['name' ]) # test-svc + if_name = str(resource_value['if_name' ]) # ethernet-1/1 + sif_index = int(resource_value['sif_index']) # 0 + if_id = '{:s}.{:d}'.format(if_name, sif_index) + + if delete: + PATH_TMPL = '/network-instances/network-instance[name={:s}]/interfaces/interface[id={:s}]' + str_path = PATH_TMPL.format(ni_name, if_id) + str_data = json.dumps({}) + return str_path, str_data + + str_path = '/network-instances/network-instance[name={:s}]/interfaces/interface[id={:s}]'.format(ni_name, if_id) + str_data = json.dumps({ + 'id': if_id, + 'config': {'id': if_id, 'interface': if_name, 'subinterface': sif_index}, + }) + return str_path, str_data + + def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: + response = [] + return response diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py new file mode 100644 index 0000000000000000000000000000000000000000..9d75e9ac66e023c8f7be44d892cb6eec647761eb --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py @@ -0,0 +1,61 @@ +# 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. + +import json, logging +from typing import Any, Dict, List, Tuple +from ._Handler import _Handler + +LOGGER = logging.getLogger(__name__) + +class NetworkInstanceStaticRouteHandler(_Handler): + def get_resource_key(self) -> str: return '/network_instance/static_route' + def get_path(self) -> str: return '/network-instances/network-instance/static_route' + + def compose(self, resource_key : str, resource_value : Dict, delete : bool = False) -> Tuple[str, str]: + ni_name = str(resource_value['name' ]) # test-svc + prefix = str(resource_value['prefix' ]) # '172.0.1.0/24' + + identifier = 'STATIC' + name = 'static' + if delete: + PATH_TMPL = '/network-instances/network-instance[name={:s}]/protocols' + PATH_TMPL += '/protocol[identifier={:s}][name={:s}]/static-routes/static[prefix={:s}]' + str_path = PATH_TMPL.format(ni_name, identifier, name, prefix) + str_data = json.dumps({}) + return str_path, str_data + + next_hop = str(resource_value['next_hop' ]) # '172.0.0.1' + next_hop_index = int(resource_value.get('next_hop_index', 0)) # 0 + + PATH_TMPL = '/network-instances/network-instance[name={:s}]/protocols/protocol[identifier={:s}][name={:s}]' + str_path = PATH_TMPL.format(ni_name, identifier, name) + str_data = json.dumps({ + 'identifier': identifier, 'name': name, + 'config': {'identifier': identifier, 'name': name, 'enabled': True}, + 'static_routes': {'static': [{ + 'prefix': prefix, + 'config': {'prefix': prefix}, + 'next_hops': { + 'next-hop': [{ + 'index': next_hop_index, + 'config': {'index': next_hop_index, 'next_hop': next_hop} + }] + } + }]} + }) + return str_path, str_data + + def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: + response = [] + return response diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/Tools.py b/src/device/service/drivers/gnmi_openconfig/handlers/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..30343ac28a46a0c1d24bcb66d07fa03fa377f9fa --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/Tools.py @@ -0,0 +1,30 @@ +# 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. + +import re +from typing import Any, Dict, Iterable + +RE_REMOVE_FILTERS = re.compile(r'\[[^\]]+\]') +RE_REMOVE_NAMESPACES = re.compile(r'\/[a-zA-Z0-9\_\-]+:') + +def get_schema(resource_key : str): + resource_key = RE_REMOVE_FILTERS.sub('', resource_key) + resource_key = RE_REMOVE_NAMESPACES.sub('/', resource_key) + return resource_key + +def dict_get_first(d : Dict, field_names : Iterable[str], default=None) -> Any: + for field_name in field_names: + if field_name not in d: continue + return d[field_name] + return default diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/_Handler.py b/src/device/service/drivers/gnmi_openconfig/handlers/_Handler.py new file mode 100644 index 0000000000000000000000000000000000000000..d20c77b1165decce7ea07243beb782a6b749734b --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/_Handler.py @@ -0,0 +1,32 @@ +# 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. + +from typing import Any, Dict, List, Tuple + +class _Handler: + def get_resource_key(self) -> str: + # Retrieve the TeraFlowSDN resource_key path schema used to point this handler + raise NotImplementedError() + + def get_path(self) -> str: + # Retrieve the OpenConfig path schema used to interrogate the device + raise NotImplementedError() + + def compose(self, resource_key : str, resource_value : Dict, delete : bool = False) -> Tuple[str, str]: + # Compose a Set/Delete message based on the resource_key/resource_value fields, and the delete flag + raise NotImplementedError() + + def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: + # Parse a Reply from the device and return a list of resource_key/resource_value pairs + raise NotImplementedError() diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/__init__.py b/src/device/service/drivers/gnmi_openconfig/handlers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..39cd7c66ad5e8c16e89192ad0f2ffb7c43ae6c50 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/__init__.py @@ -0,0 +1,103 @@ +# 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. + +import logging +from typing import Dict, List, Optional, Tuple, Union +from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES +from ._Handler import _Handler +from .Component import ComponentHandler +from .Interface import InterfaceHandler +from .InterfaceCounter import InterfaceCounterHandler +from .NetworkInstance import NetworkInstanceHandler +from .NetworkInstanceInterface import NetworkInstanceInterfaceHandler +from .NetworkInstanceStaticRoute import NetworkInstanceStaticRouteHandler +from .Tools import get_schema + +LOGGER = logging.getLogger(__name__) + +comph = ComponentHandler() +ifaceh = InterfaceHandler() +ifctrh = InterfaceCounterHandler() +nih = NetworkInstanceHandler() +niifh = NetworkInstanceInterfaceHandler() +nisrh = NetworkInstanceStaticRouteHandler() + +ALL_RESOURCE_KEYS = [ + RESOURCE_ENDPOINTS, + RESOURCE_INTERFACES, + RESOURCE_NETWORK_INSTANCES, +] + +RESOURCE_KEY_MAPPER = { + RESOURCE_ENDPOINTS : comph.get_resource_key(), + RESOURCE_INTERFACES : ifaceh.get_resource_key(), + RESOURCE_NETWORK_INSTANCES : nih.get_resource_key(), +} + +PATH_MAPPER = { + '/components' : comph.get_path(), + '/interfaces' : ifaceh.get_path(), + '/network-instances' : nih.get_path(), +} + +RESOURCE_KEY_TO_HANDLER = { + comph.get_resource_key() : comph, + ifaceh.get_resource_key() : ifaceh, + ifctrh.get_resource_key() : ifctrh, + nih.get_resource_key() : nih, + niifh.get_resource_key() : niifh, + nisrh.get_resource_key() : nisrh, +} + +PATH_TO_HANDLER = { + comph.get_path() : comph, + ifaceh.get_path() : ifaceh, + ifctrh.get_path() : ifctrh, + nih.get_path() : nih, + niifh.get_path() : niifh, + nisrh.get_path() : nisrh, +} + +def get_handler( + resource_key : Optional[str] = None, path : Optional[str] = None, raise_if_not_found=True +) -> Optional[_Handler]: + if (resource_key is None) == (path is None): + MSG = 'Exactly one of resource_key({:s}) or path({:s}) must be specified' + raise Exception(MSG.format(str(resource_key), str(path))) # pylint: disable=broad-exception-raised + if resource_key is not None: + resource_key_schema = get_schema(resource_key) + resource_key_schema = RESOURCE_KEY_MAPPER.get(resource_key_schema, resource_key_schema) + handler = RESOURCE_KEY_TO_HANDLER.get(resource_key_schema) + if handler is None and raise_if_not_found: + MSG = 'Handler not found: resource_key={:s} resource_key_schema={:s}' + # pylint: disable=broad-exception-raised + raise Exception(MSG.format(str(resource_key), str(resource_key_schema))) + elif path is not None: + path_schema = get_schema(path) + path_schema = PATH_MAPPER.get(path_schema, path_schema) + handler = PATH_TO_HANDLER.get(path_schema) + if handler is None and raise_if_not_found: + MSG = 'Handler not found: resource_key={:s} resource_key_schema={:s}' + # pylint: disable=broad-exception-raised + raise Exception(MSG.format(str(resource_key), str(resource_key_schema))) + return handler + +def get_path(resource_key : str) -> str: + return get_handler(resource_key=resource_key).get_path() + +def parse(str_path : str, value : Union[Dict, List]): + return get_handler(path=str_path).parse(value) + +def compose(resource_key : str, resource_value : Union[Dict, List], delete : bool = False) -> Tuple[str, str]: + return get_handler(resource_key=resource_key).compose(resource_key, resource_value, delete=delete) diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/old_bgp_handler.txt b/src/device/service/drivers/gnmi_openconfig/handlers/old_bgp_handler.txt new file mode 100644 index 0000000000000000000000000000000000000000..595a19788bce7ff0990346ef249ad3ce71f55efa --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/old_bgp_handler.txt @@ -0,0 +1,138 @@ +# 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. + + +# WARNING: this handler is work in progress. Use with care! + +import logging, json +from typing import Any, Dict, List, Tuple + +LOGGER = logging.getLogger(__name__) + +class NetworkInstanceHandler: + def get_resource_key(self) -> str: return '/network_instance' + def get_path(self) -> str: return '/network-instances/network-instance' + + def compose_set(self, resource_key : str, resource_value : Dict) -> Tuple[str, str]: + ni_name = str(resource_value['name']) # test-svc + ni_type = str(resource_value['type']) # L3VRF / + + if_name = str (resource_value['name' ]) # ethernet-1/1 + if_enabled = bool(resource_value.get('enabled' , True)) # True/False + sif_index = int (resource_value.get('sub_if_index' , 0 )) # 0 + sif_enabled = bool(resource_value.get('sub_if_enabled' , True)) # True/False + sif_ipv4_enabled = bool(resource_value.get('sub_if_ipv4_enabled', True)) # True/False + sif_ipv4_address = str (resource_value['sub_if_ipv4_address' ]) # 172.16.0.1 + sif_ipv4_prefix = int (resource_value['sub_if_ipv4_prefix' ]) # 24 + + str_path = '/interfaces/interface[name={:s}]'.format(if_name) + str_data = json.dumps({ + "name": if_name, + "config": {"name": if_name, "enabled": if_enabled}, + "subinterfaces": { + "subinterface": { + "index": sif_index, + "config": {"index": sif_index, "enabled": sif_enabled}, + "ipv4": { + "config": {"enabled": sif_ipv4_enabled}, + "addresses": { + "address": { + "ip": sif_ipv4_address, + "config": {"ip": sif_ipv4_address, "prefix_length": sif_ipv4_prefix}, + } + } + } + } + } + }) + return str_path, str_data + + + #oc_ni = openconfig_network_instance() + #ni = oc_ni.network_instances.network_instance.add(name=ni_name) + #ni.config.name = ni_name + + #ni_desc = resource_value.get('description') + #if ni_desc is not None: ni.config.description = ni_desc + + #if ni_type == 'L3VRF': + # ni.config.type = 'L3VRF' + # #ni_router_id = resource_value.get('router_id') + # #if ni_router_id is not None: ni.config.router_id = ni_router_id + + # proto_bgp = ni.protocols.protocol.add(identifier='BGP', name=ni_name) + # proto_bgp.config.identifier = 'BGP' + # proto_bgp.config.name = ni_name + # proto_bgp.config.enabled = True + # proto_bgp.bgp.global_.config.as_ = 65000 + # proto_bgp.bgp.global_.config.router_id = '172.0.0.1' + + # #ni.config.route_distinguisher = resource_value['route_distinguisher'] + #elif ni_type == 'L3VRF': + # pass + #else: + # raise NotImplementedError() + + #str_path = '/network-instances/network-instance[name={:s}]'.format(ni_name) + #str_data = pybindJSON.dumps(ni, mode='default') + + #str_path = '/network-instances/network-instance[name={:s}]/protocols/protocol[identifier=BGP][name=BGP]'.format(ni_name) + #str_data = json.dumps({ + # "identifier": "BGP", + # "name": "BGP", + # "config": {"identifier": "BGP", "name": "BGP", "enabled": True}, + # "bgp": {"global": {"config": {"as": 65000, "router-id": "5.5.5.5"}}} + #}) + + str_path = '/network-instances/network-instance[name=test-svc]' + str_data = json.dumps({ + "name": "test-svc", + "config": { + "name": "test-svc", + "type": "openconfig-network-instance-types:L3VRF" + }, + "protocols": { + "protocol": [ + { + "identifier": "DIRECTLY_CONNECTED", + "name": "DIRECTLY-CONNECTED", + "config": {"identifier": "DIRECTLY_CONNECTED", "name": "DIRECTLY-CONNECTED", "enabled": True}, + }, + { + "identifier": "STATIC", + "name": "static", + "config": {"identifier": "STATIC", "name": "static", "enabled": True}, + "static_routes": { + "static": [ + { + "prefix": "172.0.1.0/24", + "config": {"prefix": "172.0.1.0/24"}, + "next_hops": { + "next-hop": [{"index": 0, "config": {"index": 0, "next_hop": "172.0.0.1"}}] + } + } + ] + } + } + ] + }, + }) + + + #str_path = '/network-instances/network-instance[name={:s}]/protocols/protocol[identifier=DIRECTLY_CONNECTED][name=DIR]'.format(ni_name) + #str_data = json.dumps({ + # "identifier": "DIRECTLY_CONNECTED", + # "name": "DIR", + # "config": {"identifier": "DIRECTLY_CONNECTED", "name": "DIR", "enabled": True}, + #}) diff --git a/src/device/service/drivers/gnmi_openconfig/tools/Capabilities.py b/src/device/service/drivers/gnmi_openconfig/tools/Capabilities.py new file mode 100644 index 0000000000000000000000000000000000000000..b90bf3db887874d3c9015336cc105b3429c8e64e --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/tools/Capabilities.py @@ -0,0 +1,36 @@ +# 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. + +from typing import Optional, Set, Union +from common.tools.grpc.Tools import grpc_message_to_json +from ..gnmi.gnmi_pb2 import CapabilityRequest # pylint: disable=no-name-in-module +from ..gnmi.gnmi_pb2_grpc import gNMIStub + +def get_supported_encodings( + stub : gNMIStub, username : str, password : str, timeout : Optional[int] = None +) -> Set[Union[str, int]]: + metadata = [('username', username), ('password', password)] + req = CapabilityRequest() + reply = stub.Capabilities(req, metadata=metadata, timeout=timeout) + + data = grpc_message_to_json(reply) + supported_encodings = { + supported_encoding + for supported_encoding in data.get('supported_encodings', []) + if isinstance(supported_encoding, str) + } + if len(supported_encodings) == 0: + # pylint: disable=broad-exception-raised + raise Exception('No supported encodings found') + return supported_encodings diff --git a/src/device/service/drivers/gnmi_openconfig/tools/Channel.py b/src/device/service/drivers/gnmi_openconfig/tools/Channel.py new file mode 100644 index 0000000000000000000000000000000000000000..264dd032166117873702643e17acb344b408b194 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/tools/Channel.py @@ -0,0 +1,34 @@ +# 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. + +import grpc, logging, ssl + +def get_grpc_channel(address : str, port : int, use_tls : bool, logger : logging.Logger) -> grpc.Channel: + endpoint = str(address) + ':' + str(port) + logger.info('Connecting gNMI {:s}...'.format(endpoint)) + if use_tls: + logger.debug('Getting server certificate...') + str_server_certificate = ssl.get_server_certificate((str(address), int(port))) + bytes_server_certificate = str_server_certificate.encode('UTF-8') + logger.debug('Using secure SSL channel...') + credentials = grpc.ssl_channel_credentials( + root_certificates=bytes_server_certificate, private_key=None, certificate_chain=None) + options = [ + #('grpc.ssl_target_name_override', options.altName,) + ] + channel = grpc.secure_channel(endpoint, credentials, options) + else: + logger.debug('Using insecure channel...') + channel = grpc.insecure_channel(endpoint) + return channel diff --git a/src/device/service/drivers/gnmi_openconfig/tools/Path.py b/src/device/service/drivers/gnmi_openconfig/tools/Path.py new file mode 100644 index 0000000000000000000000000000000000000000..40ab28dc6bbaf8a65b667804dfe9285f36864e29 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/tools/Path.py @@ -0,0 +1,98 @@ +# 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. + +import re +from typing import List +from ..gnmi.gnmi_pb2 import Path, PathElem + +RE_PATH_SPLIT = re.compile(r'/(?=(?:[^\[\]]|\[[^\[\]]+\])*$)') +RE_PATH_KEYS = re.compile(r'\[(.*?)\]') + +def path_from_string(path='/'): + if not path: return Path(elem=[]) + + if path[0] == '/': + if path[-1] == '/': + path_list = RE_PATH_SPLIT.split(path)[1:-1] + else: + path_list = RE_PATH_SPLIT.split(path)[1:] + else: + if path[-1] == '/': + path_list = RE_PATH_SPLIT.split(path)[:-1] + else: + path_list = RE_PATH_SPLIT.split(path) + + path = [] + for elem in path_list: + elem_name = elem.split('[', 1)[0] + elem_keys = RE_PATH_KEYS.findall(elem) + dict_keys = dict(x.split('=', 1) for x in elem_keys) + path.append(PathElem(name=elem_name, key=dict_keys)) + + return Path(elem=path) + +def path_to_string(path : Path) -> str: + path_parts = list() + for elem in path.elem: + kv_list = list() + for key in elem.key: + value = elem.key[key] + kv = '{:s}={:s}'.format(key, value) + kv_list.append(kv) + + path_part_name = elem.name + if len(kv_list) == 0: + path_parts.append(path_part_name) + else: + str_kv = ', '.join(kv_list) + path_part = '{:s}[{:s}]'.format(path_part_name, str_kv) + path_parts.append(path_part) + + str_path = '/{:s}'.format('/'.join(path_parts)) + return str_path + +def parse_xpath(xpath : str) -> str: + xpath = xpath.replace('//', '/') + xpath = xpath.replace('oci:interface[', 'interface[') + xpath = xpath.replace('/oci', '/openconfig-interfaces') + xpath = re.sub(r"\[oci:name='(.*?)'\]", r"[name=\1]", xpath) + # Eliminar el contador del final + xpath = '/'.join(xpath.split('/')[:-1]) + '/' + return xpath + +def split_resource_key(path): + pattern = r'/state/counters/(.*)' + match = re.search(pattern, path) + if match is None: return None + return match.group(1) + +def dict_to_xpath(d: dict) -> str: + xpath = '/' + for item in d['elem']: + name = item.get('name') + if name == 'interface': + key = item.get('key') + interface_name = key.get('name') + xpath += f"/oci:interface[oci:name='{interface_name}']" + else: + xpath += f"/{name}" + xpath = xpath.replace('openconfig-interfaces', 'oci') + return xpath + +def compose_path(base_path : str, path_filters : List[str] = []): + new_path = '' if base_path is None else str(base_path) + for path_filter in path_filters: + if path_filter == '': continue + new_path = '{:s}[{:s}]'.format(new_path, path_filter) + return new_path diff --git a/src/device/service/drivers/gnmi_openconfig/tools/Subscriptions.py b/src/device/service/drivers/gnmi_openconfig/tools/Subscriptions.py new file mode 100644 index 0000000000000000000000000000000000000000..18b6445ae31c00495c4f3a84922c9c8d4198b3f7 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/tools/Subscriptions.py @@ -0,0 +1,47 @@ +# 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. + +# Collection of samples through NetConf is very slow and each request collects all the data. +# Populate a cache periodically (when first interface is interrogated). +# Evict data after some seconds, when data is considered as outdated + +import anytree +from typing import Any, List +from device.service.driver_api.AnyTreeTools import TreeNode, get_subnode, set_subnode_value + +class Subscriptions: + def __init__(self) -> None: + self.__resolver = anytree.Resolver(pathattr='name') + self.__subscriptions = TreeNode('.') + + def add( + self, resource_path : List[str], sampling_duration : float, sampling_interval : float, value : Any + ) -> None: + subscription_path = resource_path + ['{:.3f}:{:.3f}'.format(sampling_duration, sampling_interval)] + set_subnode_value(self.__resolver, self.__subscriptions, subscription_path, value) + + def get( + self, resource_path : List[str], sampling_duration : float, sampling_interval : float + ) -> TreeNode: + subscription_path = resource_path + ['{:.3f}:{:.3f}'.format(sampling_duration, sampling_interval)] + value = get_subnode(self.__resolver, self.__subscriptions, subscription_path) + return value + + def delete( + self, reference : TreeNode + ) -> None: + parent : TreeNode = reference.parent + children = list(parent.children) + children.remove(reference) + parent.children = tuple(children) diff --git a/src/device/service/drivers/gnmi_openconfig/tools/Value.py b/src/device/service/drivers/gnmi_openconfig/tools/Value.py new file mode 100644 index 0000000000000000000000000000000000000000..4797930a17360d8a780e99ea9ac05c0e3a1f7abc --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/tools/Value.py @@ -0,0 +1,52 @@ +# 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. + +import base64, json +from typing import Any +from ..gnmi.gnmi_pb2 import TypedValue + +def decode_value(value : TypedValue) -> Any: + encoding = value.WhichOneof('value') + if encoding == 'json_val': + value = value.json_val + #mdl, cls = self._classes[className] + #obj = json.loads(strObj) + #if isinstance(obj, (list,)): + # obj = map(lambda n: pybindJSON.loads(n, mdl, cls.__name__), obj) + # data = map(lambda n: json.loads(pybindJSON.dumps(n, mode='default')), obj) + #else: + # obj = pybindJSON.loads(obj, mdl, cls.__name__) + # data = json.loads(pybindJSON.dumps(obj, mode='default')) + raise NotImplementedError() + #return value + elif encoding == 'json_ietf_val': + value : str = value.json_ietf_val + try: + return json.loads(value) + except json.decoder.JSONDecodeError: + # Assume is Base64-encoded + b_b64_value = value.encode('UTF-8') + b_value = base64.b64decode(b_b64_value, validate=True) + value = b_value.decode('UTF-8') + return json.loads(value) + else: + MSG = 'Unsupported Encoding({:s}) in Value({:s})' + # pylint: disable=broad-exception-raised + raise Exception(MSG.format(str(encoding), str(value))) + +def value_exists(value) -> bool: + if value is None: return False + if isinstance(value, Exception): return False + if issubclass(type(value), Exception): return False + return True diff --git a/src/device/service/drivers/gnmi_openconfig/tools/__init__.py b/src/device/service/drivers/gnmi_openconfig/tools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/tools/__init__.py @@ -0,0 +1,14 @@ +# 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. + diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py index c70dafe9462763310f55368b60232c3733bbd825..b34efbc8fbcc8e4a340e8c2282268b9f0246fddc 100644 --- a/src/device/service/drivers/openconfig/OpenConfigDriver.py +++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py @@ -62,7 +62,7 @@ class NetconfSessionHandler: self.__username = settings.get('username') self.__password = settings.get('password') self.__vendor = settings.get('vendor') - self.__version = settings.get('version', "1") + self.__version = settings.get('version', "1") self.__key_filename = settings.get('key_filename') self.__hostkey_verify = settings.get('hostkey_verify', True) self.__look_for_keys = settings.get('look_for_keys', True) diff --git a/src/device/service/drivers/openconfig/templates/ACL/__init__.py b/src/device/service/drivers/openconfig/templates/ACL/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/device/service/drivers/openconfig/templates/ACL/__init__.py @@ -0,0 +1,14 @@ +# 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. + diff --git a/src/device/service/drivers/openconfig/templates/VPN/__init__.py b/src/device/service/drivers/openconfig/templates/VPN/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/device/service/drivers/openconfig/templates/VPN/__init__.py @@ -0,0 +1,14 @@ +# 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. + diff --git a/src/device/tests/test_gnmi.py b/src/device/tests/test_gnmi.py new file mode 100644 index 0000000000000000000000000000000000000000..50c9155822d5285fda5fc75777363c066ffb215a --- /dev/null +++ b/src/device/tests/test_gnmi.py @@ -0,0 +1,115 @@ +# 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. + +import logging, os, sys, time +from typing import Dict, Tuple +os.environ['DEVICE_EMULATED_ONLY'] = 'YES' +from device.service.drivers.gnmi_openconfig.GnmiOpenConfigDriver import GnmiOpenConfigDriver # pylint: disable=wrong-import-position +#from device.service.driver_api._Driver import ( +# RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES, RESOURCE_ROUTING_POLICIES, RESOURCE_SERVICES +#) + +logging.basicConfig(level=logging.DEBUG) +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +# +---+---------------------------+--------------+---------------------------------+-------+---------+--------------------+--------------+ +# | # | Name | Container ID | Image | Kind | State | IPv4 Address | IPv6 Address | +# +---+---------------------------+--------------+---------------------------------+-------+---------+--------------------+--------------+ +# | 1 | clab-tfs-scenario-client1 | a8d48ec3265a | ghcr.io/hellt/network-multitool | linux | running | 172.100.100.201/24 | N/A | +# | 2 | clab-tfs-scenario-client2 | fc88436d2b32 | ghcr.io/hellt/network-multitool | linux | running | 172.100.100.202/24 | N/A | +# | 3 | clab-tfs-scenario-srl1 | b995b9bdadda | ghcr.io/nokia/srlinux | srl | running | 172.100.100.101/24 | N/A | +# | 4 | clab-tfs-scenario-srl2 | aacfc38cc376 | ghcr.io/nokia/srlinux | srl | running | 172.100.100.102/24 | N/A | +# +---+---------------------------+--------------+---------------------------------+-------+---------+--------------------+--------------+ + +def interface(if_name, sif_index, ipv4_address, ipv4_prefix, enabled) -> Tuple[str, Dict]: + str_path = '/interface[{:s}]'.format(if_name) + str_data = {'name': if_name, 'enabled': enabled, 'sub_if_index': sif_index, 'sub_if_enabled': enabled, + 'sub_if_ipv4_enabled': enabled, 'sub_if_ipv4_address': ipv4_address, 'sub_if_ipv4_prefix': ipv4_prefix} + return str_path, str_data + +def network_instance(ni_name, ni_type) -> Tuple[str, Dict]: + str_path = '/network_instance[{:s}]'.format(ni_name) + str_data = {'name': ni_name, 'type': ni_type} + return str_path, str_data + +def network_instance_static_route(ni_name, prefix, next_hop, next_hop_index=0) -> Tuple[str, Dict]: + str_path = '/network_instance[{:s}]/static_route[{:s}]'.format(ni_name, prefix) + str_data = {'name': ni_name, 'prefix': prefix, 'next_hop': next_hop, 'next_hop_index': next_hop_index} + return str_path, str_data + +def network_instance_interface(ni_name, if_name, sif_index) -> Tuple[str, Dict]: + str_path = '/network_instance[{:s}]/interface[{:s}.{:d}]'.format(ni_name, if_name, sif_index) + str_data = {'name': ni_name, 'if_name': if_name, 'sif_index': sif_index} + return str_path, str_data + +def main(): + driver_settings = { + 'protocol': 'gnmi', + 'username': 'admin', + 'password': 'NokiaSrl1!', + 'use_tls' : True, + } + driver = GnmiOpenConfigDriver('172.100.100.102', 57400, **driver_settings) + driver.Connect() + + #resources_to_get = [] + #resources_to_get = [RESOURCE_ENDPOINTS] + #resources_to_get = [RESOURCE_INTERFACES] + #resources_to_get = [RESOURCE_NETWORK_INSTANCES] + #resources_to_get = [RESOURCE_ROUTING_POLICIES] + #resources_to_get = [RESOURCE_SERVICES] + #LOGGER.info('resources_to_get = {:s}'.format(str(resources_to_get))) + #results_getconfig = driver.GetConfig(resources_to_get) + #LOGGER.info('results_getconfig = {:s}'.format(str(results_getconfig))) + + #resources_to_set = [ + # network_instance('test-svc', 'L3VRF'), + # + # interface('ethernet-1/1', 0, '172.16.0.1', 24, True), + # network_instance_interface('test-svc', 'ethernet-1/1', 0), + # + # interface('ethernet-1/2', 0, '172.0.0.1', 24, True), + # network_instance_interface('test-svc', 'ethernet-1/2', 0), + # + # network_instance_static_route('test-svc', '172.0.0.0/24', '172.16.0.2'), + # network_instance_static_route('test-svc', '172.2.0.0/24', '172.16.0.3'), + #] + #LOGGER.info('resources_to_set = {:s}'.format(str(resources_to_set))) + #results_setconfig = driver.SetConfig(resources_to_set) + #LOGGER.info('results_setconfig = {:s}'.format(str(results_setconfig))) + + resources_to_delete = [ + #network_instance_static_route('d35fc1d9', '172.0.0.0/24', '172.16.0.2'), + #network_instance_static_route('d35fc1d9', '172.2.0.0/24', '172.16.0.3'), + + #network_instance_interface('d35fc1d9', 'ethernet-1/1', 0), + #network_instance_interface('d35fc1d9', 'ethernet-1/2', 0), + + interface('ethernet-1/1', 0, '172.16.1.1', 24, True), + interface('ethernet-1/2', 0, '172.0.0.2', 24, True), + + network_instance('20f66fb5', 'L3VRF'), + ] + LOGGER.info('resources_to_delete = {:s}'.format(str(resources_to_delete))) + results_deleteconfig = driver.DeleteConfig(resources_to_delete) + LOGGER.info('results_deleteconfig = {:s}'.format(str(results_deleteconfig))) + + time.sleep(1) + + driver.Disconnect() + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/device/tests/test_netconf.py b/src/device/tests/test_netconf.py new file mode 100644 index 0000000000000000000000000000000000000000..70551eed759867d26b46d399fba998a2d8007d21 --- /dev/null +++ b/src/device/tests/test_netconf.py @@ -0,0 +1,31 @@ +# 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. + +from ncclient.manager import Manager, connect_ssh + +str_filter = ''' + + + +''' + +_manager : Manager = connect_ssh( + host='10.5.32.3', port=830, username='admin', password='admin', + device_params={'name': 'huaweiyang'}, manager_params={'timeout': 120}, + key_filename=None, hostkey_verify=False, allow_agent=False, + look_for_keys=False) +c = _manager.get(filter=str_filter, with_defaults=None).data_xml +with open('data.xml', 'w') as f: + f.write(c) +_manager.close_session() diff --git a/src/monitoring/service/EventTools.py b/src/monitoring/service/EventTools.py index 0d351eee968684f18571f0da9f094a806f577efd..be3fe9b92c4867b66c562b8f0f0b35148e249cac 100644 --- a/src/monitoring/service/EventTools.py +++ b/src/monitoring/service/EventTools.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import grpc, json, logging, queue, threading from typing import Dict -import grpc, logging, queue, threading from common.method_wrappers.ServiceExceptions import ServiceException from common.proto import monitoring_pb2 -from common.proto.context_pb2 import DeviceOperationalStatusEnum, Empty, EventTypeEnum +from common.proto.context_pb2 import ConfigActionEnum, DeviceOperationalStatusEnum, Empty, EventTypeEnum from common.proto.kpi_sample_types_pb2 import KpiSampleType from context.client.ContextClient import ContextClient from monitoring.client.MonitoringClient import MonitoringClient @@ -108,7 +108,21 @@ class EventsDeviceCollector: # device is not ready for monitoring continue + enabled_endpoint_names = set() + for config_rule in device.device_config.config_rules: + if config_rule.action != ConfigActionEnum.CONFIGACTION_SET: continue + if config_rule.WhichOneof('config_rule') != 'custom': continue + str_resource_key = str(config_rule.custom.resource_key) + if not str_resource_key.startswith('/interface['): continue + json_resource_value = json.loads(config_rule.custom.resource_value) + if 'name' not in json_resource_value: continue + if 'enabled' not in json_resource_value: continue + if not json_resource_value['enabled']: continue + enabled_endpoint_names.add(json_resource_value['name']) + for endpoint in device.device_endpoints: + if endpoint.name not in enabled_endpoint_names: continue + endpoint_uuid = endpoint.endpoint_id.endpoint_uuid.uuid self._name_mapping.set_endpoint_name(endpoint_uuid, endpoint.name) diff --git a/src/monitoring/service/ManagementDBTools.py b/src/monitoring/service/ManagementDBTools.py index a2beddccd633a1961238a6affa4b8d069c26762e..6c0a69e0ec6ec22a9fff24a1de073f2df03e2115 100644 --- a/src/monitoring/service/ManagementDBTools.py +++ b/src/monitoring/service/ManagementDBTools.py @@ -261,10 +261,10 @@ class ManagementDB(): else: if data[0] == 1: return True - elif data[0] == 0: + elif data[0] == 0 or data[0] is None: return False else: - LOGGER.debug(f"KPI {kpi_id} is wrong") + LOGGER.debug(f"KPI {kpi_id} is wrong: {str(data)}") return None except sqlite3.Error as e: LOGGER.debug(f"KPI {kpi_id} cannot be checked from the ManagementDB: {e}") diff --git a/src/monitoring/service/__main__.py b/src/monitoring/service/__main__.py index d0a132c70bed2c56bc9159ec3ad284120c0eb623..d242f0fc0add7afebed32178bfafde23552a7649 100644 --- a/src/monitoring/service/__main__.py +++ b/src/monitoring/service/__main__.py @@ -49,7 +49,7 @@ def start_monitoring(name_mapping : NameMapping): monitor_kpi_request = monitoring_pb2.MonitorKpiRequest() monitor_kpi_request.kpi_id.CopyFrom(kpi_id) monitor_kpi_request.monitoring_window_s = 86400 - monitor_kpi_request.sampling_rate_s = 30 + monitor_kpi_request.sampling_rate_s = 10 events_collector._monitoring_client.MonitorKpi(monitor_kpi_request) time.sleep(0.5) # let other tasks run; do not overload CPU diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py index 91367e23f29a02aa3e9605fcd0d2864b9191d800..c6621773b8b45230358eebff2c60b95aacdda31f 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py +++ b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py @@ -19,7 +19,8 @@ from common.tools.object_factory.ConfigRule import json_config_rule_set SETTINGS_RULE_NAME = '/settings' -DEV_EP_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/endpoint\[([^\]]+)\]\/settings') +DEVICE_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/settings') +ENDPOINT_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/endpoint\[([^\]]+)\]\/settings') L2NM_SETTINGS_FIELD_DEFAULTS = { 'encapsulation_type': 'dot1q', @@ -76,26 +77,37 @@ def compose_device_config_rules( device_name_mapping : Dict[str, str], endpoint_name_mapping : Dict[Tuple[str, str], str] ) -> None: + devices_traversed = set() endpoints_traversed = set() for path_hop in path_hops: device_uuid_or_name = path_hop['device'] + devices_traversed.add(device_uuid_or_name) endpoints_traversed.add((device_uuid_or_name, path_hop['ingress_ep'])) endpoints_traversed.add((device_uuid_or_name, path_hop['egress_ep'])) for config_rule in config_rules: if config_rule.WhichOneof('config_rule') != 'custom': continue - match = DEV_EP_SETTINGS.match(config_rule.custom.resource_key) - if match is None: continue - device_uuid_or_name = match.group(1) - device_name_or_uuid = device_name_mapping[device_uuid_or_name] - device_keys = {device_uuid_or_name, device_name_or_uuid} - - endpoint_uuid_or_name = match.group(2) - endpoint_name_or_uuid_1 = endpoint_name_mapping[(device_uuid_or_name, endpoint_uuid_or_name)] - endpoint_name_or_uuid_2 = endpoint_name_mapping[(device_name_or_uuid, endpoint_uuid_or_name)] - endpoint_keys = {endpoint_uuid_or_name, endpoint_name_or_uuid_1, endpoint_name_or_uuid_2} - - device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys)) - if len(device_endpoint_keys.intersection(endpoints_traversed)) == 0: continue - subservice_config_rules.append(config_rule) + match = DEVICE_SETTINGS.match(config_rule.custom.resource_key) + if match is not None: + device_uuid_or_name = match.group(1) + device_name_or_uuid = device_name_mapping[device_uuid_or_name] + device_keys = {device_uuid_or_name, device_name_or_uuid} + + if len(device_keys.intersection(devices_traversed)) == 0: continue + subservice_config_rules.append(config_rule) + + match = ENDPOINT_SETTINGS.match(config_rule.custom.resource_key) + if match is not None: + device_uuid_or_name = match.group(1) + device_name_or_uuid = device_name_mapping[device_uuid_or_name] + device_keys = {device_uuid_or_name, device_name_or_uuid} + + endpoint_uuid_or_name = match.group(2) + endpoint_name_or_uuid_1 = endpoint_name_mapping[(device_uuid_or_name, endpoint_uuid_or_name)] + endpoint_name_or_uuid_2 = endpoint_name_mapping[(device_name_or_uuid, endpoint_uuid_or_name)] + endpoint_keys = {endpoint_uuid_or_name, endpoint_name_or_uuid_1, endpoint_name_or_uuid_2} + + device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys)) + if len(device_endpoint_keys.intersection(endpoints_traversed)) == 0: continue + subservice_config_rules.append(config_rule) diff --git a/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py b/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py index cf41f186f4f35c45cbb1ce836196272327f6048f..eb9e862753d3834f7dd7b7c6529d7d9ae5e38f1f 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py +++ b/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py @@ -111,14 +111,18 @@ def eropath_to_hops( if num_ero_hops - 1 == len(path_hops): break - endpoint_key = (last_hop['device'], last_hop['egress_ep']) - link_tuple = endpoint_to_link_dict.get(endpoint_key) + endpoint_key = (last_hop['device'], last_hop['egress_ep'], 'src') + link_tuple = endpoint_to_link_dict[endpoint_key] + if link_tuple is None: raise Exception('Malformed path') + ingress = next(iter([ ep_id for ep_id in link_tuple[0]['link_endpoint_ids'] if (ep_id['endpoint_id']['device_id'] == device_uuid) and\ (ep_id['endpoint_id']['endpoint_uuid'] != endpoint_uuid) ]), None) - if ingress['endpoint_id']['device_id'] != device_uuid: raise Exception('Malformed path') + if ingress['endpoint_id']['device_id'] != device_uuid: + raise Exception('Malformed path') + ingress_ep = ingress['endpoint_id']['endpoint_uuid'] ingress_ep = MAP_TAPI_UUIDS.get(ingress_ep, ingress_ep) path_hops.append({ diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py index 3ec71dc64536e28457c4f1adbf3679186285786d..f86412a8c736fda4d2fff2b485453cc3bf1ce0b1 100644 --- a/src/service/service/service_handler_api/FilterFields.py +++ b/src/service/service/service_handler_api/FilterFields.py @@ -35,6 +35,7 @@ DEVICE_DRIVER_VALUES = { DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352, DeviceDriverEnum.DEVICEDRIVER_XR, DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN, + DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG, } # Map allowed filter fields to allowed values per Filter field. If no restriction (free text) None is specified diff --git a/src/service/service/service_handler_api/SettingsHandler.py b/src/service/service/service_handler_api/SettingsHandler.py index 2527347768fd70dfe68d92cf99329a67b617f6ce..b8daec7d7b5fc857aef9aa5e46bb04d33a02b47c 100644 --- a/src/service/service/service_handler_api/SettingsHandler.py +++ b/src/service/service/service_handler_api/SettingsHandler.py @@ -57,6 +57,16 @@ class SettingsHandler: def get(self, key_or_path : Union[str, List[str]], default : Optional[Any] = None) -> Optional[TreeNode]: return get_subnode(self.__resolver, self.__config, key_or_path, default=default) + def get_device_settings(self, device : Device) -> Optional[TreeNode]: + device_keys = device.device_id.device_uuid.uuid, device.name + + for device_key in device_keys: + endpoint_settings_uri = '/device[{:s}]/settings'.format(device_key) + endpoint_settings = self.get(endpoint_settings_uri) + if endpoint_settings is not None: return endpoint_settings + + return None + def get_endpoint_settings(self, device : Device, endpoint : EndPoint) -> Optional[TreeNode]: device_keys = device.device_id.device_uuid.uuid, device.name endpoint_keys = endpoint.endpoint_id.endpoint_uuid.uuid, endpoint.name diff --git a/src/service/service/service_handler_api/Tools.py b/src/service/service/service_handler_api/Tools.py index 787b0f499a2d4b3ad76bfe4b7d41f072bbe6c50c..b06d128d94328b0d664e8928e1701ee327aa6d03 100644 --- a/src/service/service/service_handler_api/Tools.py +++ b/src/service/service/service_handler_api/Tools.py @@ -11,6 +11,7 @@ # 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 functools, re from typing import Any, List, Optional, Tuple, Union from common.method_wrappers.ServiceExceptions import NotFoundException diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index 257bc138fe932e7e5abee00981848248039d0b3f..7ea0d4f627b5d6010f6e40135f6005471efe8d71 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -19,6 +19,7 @@ from .l2nm_ietfl2vpn.L2NM_IETFL2VPN_ServiceHandler import L2NM_IETFL2VPN_Service from .l2nm_openconfig.L2NMOpenConfigServiceHandler import L2NMOpenConfigServiceHandler from .l3nm_emulated.L3NMEmulatedServiceHandler import L3NMEmulatedServiceHandler from .l3nm_openconfig.L3NMOpenConfigServiceHandler import L3NMOpenConfigServiceHandler +from .l3nm_gnmi_openconfig.L3NMGnmiOpenConfigServiceHandler import L3NMGnmiOpenConfigServiceHandler from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler from .p4.p4_service_handler import P4ServiceHandler from .tapi_tapi.TapiServiceHandler import TapiServiceHandler @@ -49,6 +50,12 @@ SERVICE_HANDLERS = [ FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG, } ]), + (L3NMGnmiOpenConfigServiceHandler, [ + { + FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L3NM, + FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG, + } + ]), (TapiServiceHandler, [ { FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, diff --git a/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py b/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py new file mode 100644 index 0000000000000000000000000000000000000000..7f3593df39f88e2e3ee806dcc33f67f9e85a6b69 --- /dev/null +++ b/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py @@ -0,0 +1,119 @@ +# 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. + +from typing import Dict, List, Optional, Tuple +from common.proto.context_pb2 import Device, EndPoint +from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set + +from service.service.service_handler_api.AnyTreeTools import TreeNode + +def _interface(if_name, sif_index, ipv4_address, ipv4_prefix, enabled) -> Tuple[str, Dict]: + str_path = '/interface[{:s}]'.format(if_name) + str_data = {'name': if_name, 'enabled': enabled, 'sub_if_index': sif_index, + 'sub_if_enabled': enabled, 'sub_if_ipv4_enabled': enabled, + 'sub_if_ipv4_address': ipv4_address, 'sub_if_ipv4_prefix': ipv4_prefix} + return str_path, str_data + +def _network_instance(ni_name, ni_type) -> Tuple[str, Dict]: + str_path = '/network_instance[{:s}]'.format(ni_name) + str_data = {'name': ni_name, 'type': ni_type} + return str_path, str_data + +def _network_instance_static_route(ni_name, prefix, next_hop, next_hop_index=0) -> Tuple[str, Dict]: + str_path = '/network_instance[{:s}]/static_route[{:s}]'.format(ni_name, prefix) + str_data = {'name': ni_name, 'prefix': prefix, 'next_hop': next_hop, 'next_hop_index': next_hop_index} + return str_path, str_data + +def _network_instance_interface(ni_name, if_name, sif_index) -> Tuple[str, Dict]: + str_path = '/network_instance[{:s}]/interface[{:s}.{:d}]'.format(ni_name, if_name, sif_index) + str_data = {'name': ni_name, 'if_name': if_name, 'sif_index': sif_index} + return str_path, str_data + +class EndpointComposer: + def __init__(self, endpoint_uuid : str) -> None: + self.uuid = endpoint_uuid + self.objekt : Optional[EndPoint] = None + self.sub_interface_index = 0 + self.ipv4_address = None + self.ipv4_prefix = None + + def configure(self, endpoint_obj : EndPoint, settings : Optional[TreeNode]) -> None: + self.objekt = endpoint_obj + if settings is None: return + json_settings : Dict = settings.value + self.ipv4_address = json_settings['ipv4_address'] + self.ipv4_prefix = json_settings['ipv4_prefix'] + self.sub_interface_index = json_settings['sub_interface_index'] + + def get_config_rules(self, network_instance_name : str, delete : bool = False) -> List[Dict]: + json_config_rule = json_config_rule_delete if delete else json_config_rule_set + return [ + json_config_rule(*_interface( + self.objekt.name, self.sub_interface_index, self.ipv4_address, self.ipv4_prefix, True + )), + json_config_rule(*_network_instance_interface( + network_instance_name, self.objekt.name, self.sub_interface_index + )), + ] + +class DeviceComposer: + def __init__(self, device_uuid : str) -> None: + self.uuid = device_uuid + self.objekt : Optional[Device] = None + self.endpoints : Dict[str, EndpointComposer] = dict() + self.static_routes : Dict[str, str] = dict() + + def get_endpoint(self, endpoint_uuid : str) -> EndpointComposer: + if endpoint_uuid not in self.endpoints: + self.endpoints[endpoint_uuid] = EndpointComposer(endpoint_uuid) + return self.endpoints[endpoint_uuid] + + def configure(self, device_obj : Device, settings : Optional[TreeNode]) -> None: + self.objekt = device_obj + if settings is None: return + json_settings : Dict = settings.value + static_routes = json_settings.get('static_routes', []) + for static_route in static_routes: + prefix = static_route['prefix'] + next_hop = static_route['next_hop'] + self.static_routes[prefix] = next_hop + + def get_config_rules(self, network_instance_name : str, delete : bool = False) -> List[Dict]: + json_config_rule = json_config_rule_delete if delete else json_config_rule_set + config_rules = [ + json_config_rule(*_network_instance(network_instance_name, 'L3VRF')) + ] + for endpoint in self.endpoints.values(): + config_rules.extend(endpoint.get_config_rules(network_instance_name, delete=delete)) + for prefix, next_hop in self.static_routes.items(): + config_rules.append( + json_config_rule(*_network_instance_static_route(network_instance_name, prefix, next_hop)) + ) + if delete: config_rules = list(reversed(config_rules)) + return config_rules + +class ConfigRuleComposer: + def __init__(self) -> None: + self.devices : Dict[str, DeviceComposer] = dict() + + def get_device(self, device_uuid : str) -> DeviceComposer: + if device_uuid not in self.devices: + self.devices[device_uuid] = DeviceComposer(device_uuid) + return self.devices[device_uuid] + + def get_config_rules(self, network_instance_name : str, delete : bool = False) -> Dict[str, List[Dict]]: + return { + device_uuid : device.get_config_rules(network_instance_name, delete=delete) + for device_uuid, device in self.devices.items() + } diff --git a/src/service/service/service_handlers/l3nm_gnmi_openconfig/L3NMGnmiOpenConfigServiceHandler.py b/src/service/service/service_handlers/l3nm_gnmi_openconfig/L3NMGnmiOpenConfigServiceHandler.py new file mode 100644 index 0000000000000000000000000000000000000000..a9cec3f42cf75a27451e953f85342e299b642685 --- /dev/null +++ b/src/service/service/service_handlers/l3nm_gnmi_openconfig/L3NMGnmiOpenConfigServiceHandler.py @@ -0,0 +1,161 @@ +# 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. + +import json, logging +from typing import Any, Dict, List, Optional, Tuple, Union +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.proto.context_pb2 import ConfigRule, DeviceId, Service +from common.tools.object_factory.Device import json_device_id +from common.type_checkers.Checkers import chk_type +from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching +from service.service.service_handler_api._ServiceHandler import _ServiceHandler +from service.service.service_handler_api.SettingsHandler import SettingsHandler +from service.service.task_scheduler.TaskExecutor import TaskExecutor +from .ConfigRuleComposer import ConfigRuleComposer + +LOGGER = logging.getLogger(__name__) + +METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'l3nm_gnmi_openconfig'}) + +class L3NMGnmiOpenConfigServiceHandler(_ServiceHandler): + def __init__( # pylint: disable=super-init-not-called + self, service : Service, task_executor : TaskExecutor, **settings + ) -> None: + self.__service = service + self.__task_executor = task_executor + self.__settings_handler = SettingsHandler(service.service_config, **settings) + self.__composer = ConfigRuleComposer() + self.__endpoint_map : Dict[Tuple[str, str], str] = dict() + + def _compose_config_rules(self, endpoints : List[Tuple[str, str, Optional[str]]]) -> None: + for endpoint in endpoints: + device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint) + + device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_settings = self.__settings_handler.get_device_settings(device_obj) + _device = self.__composer.get_device(device_obj.name) + _device.configure(device_obj, device_settings) + + endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid) + endpoint_settings = self.__settings_handler.get_endpoint_settings(device_obj, endpoint_obj) + _endpoint = _device.get_endpoint(endpoint_obj.name) + _endpoint.configure(endpoint_obj, endpoint_settings) + + self.__endpoint_map[(device_uuid, endpoint_uuid)] = device_obj.name + + def _do_configurations( + self, config_rules_per_device : Dict[str, List[Dict]], endpoints : List[Tuple[str, str, Optional[str]]], + delete : bool = False + ) -> List[Union[bool, Exception]]: + # Configuration is done atomically on each device, all OK / all KO per device + results_per_device = dict() + for device_name,json_config_rules in config_rules_per_device.items(): + try: + device_obj = self.__composer.get_device(device_name).objekt + if len(json_config_rules) == 0: continue + del device_obj.device_config.config_rules[:] + for json_config_rule in json_config_rules: + device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule)) + self.__task_executor.configure_device(device_obj) + results_per_device[device_name] = True + except Exception as e: # pylint: disable=broad-exception-caught + verb = 'deconfigure' if delete else 'configure' + MSG = 'Unable to {:s} Device({:s}) : ConfigRules({:s})' + LOGGER.exception(MSG.format(verb, str(device_name), str(json_config_rules))) + results_per_device[device_name] = e + + results = [] + for endpoint in endpoints: + device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint) + device_name = self.__endpoint_map[(device_uuid, endpoint_uuid)] + results.append(results_per_device[device_name]) + return results + + @metered_subclass_method(METRICS_POOL) + def SetEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + service_uuid = self.__service.service_id.service_uuid.uuid + #settings = self.__settings_handler.get('/settings') + self._compose_config_rules(endpoints) + network_instance_name = service_uuid.split('-')[0] + config_rules_per_device = self.__composer.get_config_rules(network_instance_name, delete=False) + results = self._do_configurations(config_rules_per_device, endpoints) + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + service_uuid = self.__service.service_id.service_uuid.uuid + #settings = self.__settings_handler.get('/settings') + self._compose_config_rules(endpoints) + network_instance_name = service_uuid.split('-')[0] + config_rules_per_device = self.__composer.get_config_rules(network_instance_name, delete=True) + results = self._do_configurations(config_rules_per_device, endpoints, delete=True) + return results + + @metered_subclass_method(METRICS_POOL) + def SetConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + results = [] + for resource in resources: + try: + resource_value = json.loads(resource[1]) + self.__settings_handler.set(resource[0], resource_value) + results.append(True) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Unable to SetConfig({:s})'.format(str(resource))) + results.append(e) + + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + results = [] + for resource in resources: + try: + self.__settings_handler.delete(resource[0]) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource))) + results.append(e) + + return results diff --git a/src/service/service/service_handlers/l3nm_gnmi_openconfig/__init__.py b/src/service/service/service_handlers/l3nm_gnmi_openconfig/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/service/service/service_handlers/l3nm_gnmi_openconfig/__init__.py @@ -0,0 +1,14 @@ +# 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. + diff --git a/src/webui/grafana_db_mon_kpis_psql.json b/src/webui/grafana_db_mon_kpis_psql.json index 845ed4296605b6a0d15f38c9a20576a93195543e..df72f9ab052c02d5a4af7e7923a96fe968116103 100644 --- a/src/webui/grafana_db_mon_kpis_psql.json +++ b/src/webui/grafana_db_mon_kpis_psql.json @@ -144,7 +144,6 @@ "options": { "legend": { "calcs": [ - "first", "min", "mean", "max", diff --git a/src/webui/service/device/forms.py b/src/webui/service/device/forms.py index 24bc92b3a5a4aec4321c07b17830f6111be7176d..e884e96a511ab90625bda257075c80adce4406cd 100644 --- a/src/webui/service/device/forms.py +++ b/src/webui/service/device/forms.py @@ -18,10 +18,10 @@ from wtforms.validators import DataRequired, Length, NumberRange, ValidationErro from common.proto.context_pb2 import DeviceOperationalStatusEnum class AddDeviceForm(FlaskForm): - device_id = StringField('ID', - validators=[DataRequired(), Length(min=5)]) + device_id = StringField('ID', validators=[DataRequired(), Length(min=5)]) device_type = SelectField('Type') operational_status = SelectField('Operational Status', coerce=int, validators=[NumberRange(min=0)]) + device_drivers_undefined = BooleanField('UNDEFINED / EMULATED') device_drivers_openconfig = BooleanField('OPENCONFIG') device_drivers_transport_api = BooleanField('TRANSPORT_API') @@ -30,9 +30,12 @@ class AddDeviceForm(FlaskForm): device_drivers_onf_tr_352 = BooleanField('ONF_TR_352') device_drivers_xr = BooleanField('XR') device_drivers_ietf_l2vpn = BooleanField('IETF L2VPN') + device_drivers_gnmi_openconfig = BooleanField('GNMI OPENCONFIG') + device_config_address = StringField('connect/address',default='127.0.0.1',validators=[DataRequired(), Length(min=5)]) device_config_port = StringField('connect/port',default='0',validators=[DataRequired(), Length(min=1)]) device_config_settings = TextAreaField('connect/settings',default='{}',validators=[DataRequired(), Length(min=2)]) + submit = SubmitField('Add') def validate_operational_status(form, field): diff --git a/src/webui/service/device/routes.py b/src/webui/service/device/routes.py index bc46847704b28fb6ef44de0aae030ccb67935928..4590c7f01a24e801ecc775ad0b22cf0dcdea3452 100644 --- a/src/webui/service/device/routes.py +++ b/src/webui/service/device/routes.py @@ -122,6 +122,8 @@ def add(): device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_XR) if form.device_drivers_ietf_l2vpn.data: device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN) + if form.device_drivers_gnmi_openconfig.data: + device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG) device_obj.device_drivers.extend(device_drivers) # pylint: disable=no-member try: diff --git a/src/webui/service/templates/device/add.html b/src/webui/service/templates/device/add.html index 6b11a19208cc9c10daa4b7615ff46ac627162573..c9165667dd8c089e91400f652177b4ca4ec98010 100644 --- a/src/webui/service/templates/device/add.html +++ b/src/webui/service/templates/device/add.html @@ -81,20 +81,17 @@ {% endfor %} {% else %} - {{ form.device_drivers_undefined }} {{ form.device_drivers_undefined.label(class="col-sm-3 - col-form-label") }} - {{ form.device_drivers_openconfig }} {{ form.device_drivers_openconfig.label(class="col-sm-3 - col-form-label") }} - {{ form.device_drivers_transport_api }} {{ form.device_drivers_transport_api.label(class="col-sm-3 - col-form-label") }} -
{{ form.device_drivers_p4 }} {{ form.device_drivers_p4.label(class="col-sm-3 col-form-label") }} - {{ form.device_drivers_ietf_network_topology }} {{ - form.device_drivers_ietf_network_topology.label(class="col-sm-3 - col-form-label") }} - {{ form.device_drivers_onf_tr_352 }} {{ form.device_drivers_onf_tr_352.label(class="col-sm-3 - col-form-label") }}
- {{ form.device_drivers_xr }} {{ form.device_drivers_xr.label(class="col-sm-3 - col-form-label") }} + {{ form.device_drivers_undefined }} {{ form.device_drivers_undefined.label(class="col-sm-3 col-form-label") }} + {{ form.device_drivers_openconfig }} {{ form.device_drivers_openconfig.label(class="col-sm-3 col-form-label") }} + {{ form.device_drivers_transport_api }} {{ form.device_drivers_transport_api.label(class="col-sm-3 col-form-label") }} +
+ {{ form.device_drivers_p4 }} {{ form.device_drivers_p4.label(class="col-sm-3 col-form-label") }} + {{ form.device_drivers_ietf_network_topology }} {{form.device_drivers_ietf_network_topology.label(class="col-sm-3 col-form-label") }} + {{ form.device_drivers_onf_tr_352 }} {{ form.device_drivers_onf_tr_352.label(class="col-sm-3 col-form-label") }} +
+ {{ form.device_drivers_xr }} {{ form.device_drivers_xr.label(class="col-sm-3 col-form-label") }} + {{ form.device_drivers_ietf_l2vpn }} {{ form.device_drivers_ietf_l2vpn.label(class="col-sm-3 col-form-label") }} + {{ form.device_drivers_gnmi_openconfig }} {{ form.device_drivers_gnmi_openconfig.label(class="col-sm-3 col-form-label") }} {% endif %}