From 5fa9fff1647c74ff152f19c642d0839567e28f2b Mon Sep 17 00:00:00 2001 From: Sachin Holla Date: Tue, 9 Jun 2020 08:01:55 -0700 Subject: [PATCH 1/4] Initial import from sonic-mgmt-framework repo 1) Moved yangs, translib, transformer, cvl and go library patch files from sonic-mgmt-framework repo. Mgmt-framework base is f789b295. These are used by both mgmt-framework and telemetry services. 2) Implemented gomodule based go dependency management. Uses vendoing to create a private copy of the libraries under "vendor" directory and all patches are applied on this copy. This happens automatically during build; vendor copy is not committed to repo. New script patches/apply.sh has been introduced to apply the patches. This can be invoked from mgmt-framework and telemetry makefiles to patch their respective vendor directories. 3) During build, two deb files are generated. No docker image is generated for this repo. - sonic-mgmt-common_1.0.0_{arch}.deb - sonic-mgmt-common-codegen_1.0.0_{arch}.deb 4) sonic-mgmt-common.deb includes yangs, cvl schema and other configuration files required by translib and cvl during runtime. Both mgmt-framework and telemetry dockers will install it. 5) sonic-mgmt-common-codegen.deb includes source files generated by ygot and yang code generators. When package cache is enabled sonic build will compile the repo only when there are some changes. So code generators may not be run always, but mgmt-framework and telemetry repo compilation requires these generated source files. Such source files are created by installing sonic-mgmt-common-codegen.deb on the build slave docker itself. This package is not installed on switch. --- .gitignore | 11 + Makefile | 89 + config/transformer/models_list | 3 + cvl/Makefile | 70 + cvl/README.md | 62 + cvl/conf/cvl_cfg.json | 20 + cvl/cvl.go | 1864 +++++++++ cvl/cvl_api.go | 514 +++ cvl/cvl_luascript.go | 65 + cvl/cvl_test.go | 3491 +++++++++++++++++ cvl/internal/util/util.go | 255 ++ cvl/internal/yparser/yparser.go | 699 ++++ cvl/jsondata_test.go | 70 + cvl/schema/Makefile | 60 + cvl/testdata/acl_rule.json | 10 + cvl/testdata/aclrule.json | 9 + cvl/testdata/acltable.json | 4 + cvl/testdata/config_db.json | 107 + cvl/testdata/config_db1.json | 133 + cvl/testdata/config_db2.json | 3437 ++++++++++++++++ cvl/testdata/create_acl_table.json | 8 + cvl/testdata/create_acl_table12.json | 8 + cvl/testdata/create_acl_table13.json | 8 + cvl/testdata/port_table.json | 165 + cvl/testdata/schema/Makefile | 64 + cvl/testdata/schema/sonic-acl-deviation.yang | 43 + cvl/testdata/schema/sonic-bgp-neighbor.yang | 84 + cvl/testdata/schema/sonic-buffer-pg.yang | 66 + cvl/testdata/schema/sonic-buffer-pool.yang | 51 + cvl/testdata/schema/sonic-buffer-profile.yang | 67 + cvl/testdata/schema/sonic-cablelength.yang | 60 + .../schema/sonic-device-metadata.yang | 97 + .../schema/sonic-device-neighbor.yang | 72 + cvl/testdata/schema/sonic-dscp-tc-map.yang | 58 + cvl/testdata/schema/sonic-mirror-session.yang | 60 + cvl/testdata/schema/sonic-pf-limits.yang | 44 + .../schema/sonic-pfc-priority-queue-map.yang | 55 + cvl/testdata/schema/sonic-port-qos-map.yang | 87 + .../schema/sonic-portchannel-interface.yang | 48 + cvl/testdata/schema/sonic-portchannel.yang | 77 + cvl/testdata/schema/sonic-queue.yang | 74 + cvl/testdata/schema/sonic-scheduler.yang | 52 + .../schema/sonic-tc-priority-group-map.yang | 54 + cvl/testdata/schema/sonic-tc-queue-map.yang | 55 + cvl/testdata/schema/sonic-vlan-deviation.yang | 36 + cvl/testdata/schema/sonic-vlan-interface.yang | 48 + cvl/testdata/schema/sonic-vlan.yang | 107 + cvl/testdata/schema/sonic-wred-profile.yang | 80 + cvl/tests/Makefile | 36 + cvl/tests/acl_rule.json | 22 + cvl/tests/cfg_validator.go | 273 ++ cvl/tests/config_db.json | 107 + cvl/tests/config_db1.json | 313 ++ cvl/tests/config_db2.json | 3437 ++++++++++++++++ cvl/tests/create_acl_table.json | 8 + cvl/tests/cv_acl.go | 443 +++ cvl/tests/cv_edit_op.go | 192 + cvl/tests/cv_vlan.go | 448 +++ cvl/tests/run_test.sh | 31 + debian/.gitignore | 9 + debian/changelog | 5 + debian/compat | 1 + debian/control | 20 + debian/rules | 4 + debian/sonic-mgmt-common-codegen.install | 5 + debian/sonic-mgmt-common.install | 14 + go.mod | 21 + go.sum | 82 + models/yang/.gitignore | 2 + models/yang/Makefile | 123 + .../annotations/openconfig-acl-annot.yang | 197 + models/yang/annotations/sonic-extensions.yang | 88 + models/yang/common/iana-if-type.yang | 1554 ++++++++ models/yang/common/ietf-inet-types.yang | 457 +++ models/yang/common/ietf-interfaces.yang | 1121 ++++++ models/yang/common/ietf-yang-types.yang | 474 +++ models/yang/common/openconfig-aaa-radius.yang | 186 + models/yang/common/openconfig-aaa-tacacs.yang | 142 + models/yang/common/openconfig-aaa-types.yang | 172 + models/yang/common/openconfig-aaa.yang | 822 ++++ .../yang/common/openconfig-alarm-types.yang | 150 + models/yang/common/openconfig-alarms.yang | 231 ++ models/yang/common/openconfig-extensions.yang | 175 + .../yang/common/openconfig-if-aggregate.yang | 232 ++ .../common/openconfig-if-ethernet-ext.yang | 117 + models/yang/common/openconfig-if-ip-ext.yang | 179 + models/yang/common/openconfig-if-poe.yang | 110 + models/yang/common/openconfig-if-tunnel.yang | 120 + models/yang/common/openconfig-if-types.yang | 108 + models/yang/common/openconfig-inet-types.yang | 343 ++ models/yang/common/openconfig-lldp-types.yang | 306 ++ models/yang/common/openconfig-messages.yang | 221 ++ .../common/openconfig-packet-match-types.yang | 309 ++ .../yang/common/openconfig-packet-match.yang | 371 ++ .../common/openconfig-platform-types.yang | 347 ++ models/yang/common/openconfig-procmon.yang | 175 + .../common/openconfig-system-logging.yang | 503 +++ .../common/openconfig-system-management.yang | 138 + .../common/openconfig-system-terminal.yang | 249 ++ models/yang/common/openconfig-types.yang | 466 +++ models/yang/common/openconfig-vlan-types.yang | 206 + models/yang/common/openconfig-vlan.yang | 449 +++ models/yang/common/openconfig-yang-types.yang | 191 + models/yang/extensions/.gitkeep | 0 models/yang/openconfig-acl.yang | 847 ++++ models/yang/openconfig-if-ethernet.yang | 438 +++ models/yang/openconfig-if-ip.yang | 1322 +++++++ models/yang/openconfig-interfaces.yang | 1067 +++++ models/yang/openconfig-lldp.yang | 660 ++++ models/yang/openconfig-platform.yang | 779 ++++ models/yang/openconfig-system.yang | 997 +++++ models/yang/sonic/common/sonic-common.yang | 50 + models/yang/sonic/common/sonic-extension.yang | 61 + models/yang/sonic/sonic-acl.yang | 221 ++ models/yang/sonic/sonic-interface.yang | 67 + models/yang/sonic/sonic-port.yang | 93 + patches/apply.sh | 48 + patches/glog.patch | 13 + patches/goyang/goyang.patch | 530 +++ patches/jsonquery.patch | 14 + patches/ygot/ygot.patch | 752 ++++ tools/pyang/pyang_plugins/yin_cvl.py | 179 + tools/xfmr/annotate.sh | 38 + translib/Makefile | 53 + translib/acl_app.go | 1712 ++++++++ translib/acl_app_test.go | 572 +++ translib/app_interface.go | 170 + translib/app_utils.go | 231 ++ translib/common_app.go | 485 +++ translib/db/db.go | 1375 +++++++ translib/db/db_test.go | 610 +++ translib/db/map.go | 123 + translib/db/subscribe.go | 275 ++ translib/db/test/arloIssue29.go | 85 + translib/db/test/testdb.go | 163 + translib/db/test/testmap.go | 102 + translib/db/test/testsubscribe.go | 88 + translib/intf_app.go | 1291 ++++++ translib/lldp_app.go | 456 +++ translib/nonyang_app.go.demo | 412 ++ translib/ocbinds/oc.go | 22 + translib/path_utils.go | 191 + translib/path_utils_test.go | 164 + translib/pfm_app.go | 431 ++ translib/request_binder.go | 266 ++ translib/request_binder_test.go | 514 +++ translib/subscribe.go | 340 ++ translib/sys_app.go | 341 ++ .../test/acl/01_create_MyACL1_MyACL2.json | 343 ++ .../test/acl/02_create_MyACL3_5Rules.json | 147 + .../acl/03_create_MyACL1_5_more_rules.json | 134 + .../acl/04_create_MyACL1_1Rule_content.json | 25 + .../test/acl/05_Create_MyACL3_binding.json | 28 + .../test/acl/08_update_AclSets_MyACL3.json | 149 + ..._after_delete_1Rule_get_MyACL4_command.txt | 2 + ...fter_delete_1Rule_get_MyACL4_response.json | 1 + .../test/acl/09_create_MyACL4_4Rules.json | 123 + .../acl/09_create_MyACL4_4Rules_command.txt | 1 + .../acl/09_delete_1Rule_MyACL4_command.txt | 1 + .../test/acl/09_get_1Rule_MyACL4_command.txt | 2 + .../acl/09_get_all_MyACL4_4Rules_command.txt | 1 + .../09_get_all_MyACL4_4Rules_response.json | 1 + .../test/interfaces/01_get_all_command.txt | 2 + .../test/interfaces/01_get_all_response.json | 1 + .../interfaces/02_get_interface_command.txt | 2 + .../interfaces/02_get_interface_response.json | 1 + .../03_get_interface_config_command.txt | 2 + .../03_get_interface_config_response.json | 1 + .../04_get_interface_state_command.txt | 2 + .../04_get_interface_state_response.json | 1 + .../test/interfaces/05_set_interface_mtu.json | 1 + .../05_set_interface_mtu_command.txt | 1 + .../interfaces/06_set_interface_ipv4.json | 1 + .../06_set_interface_ipv4_command.txt | 1 + .../interfaces/07_set_interface_ipv6.json | 1 + .../07_set_interface_ipv6_command.txt | 1 + .../08_delete_interface_ipv4_command.txt | 1 + .../09_delete_interface_ipv6_command.txt | 1 + .../test/lldp/01-get-all-intf-command.txt | 1 + .../test/lldp/01-get-all-intf-response.json | 1 + .../test/lldp/02-get-one-intf-command.txt | 1 + .../test/lldp/02-get-one-intf-response.json | 1 + .../test/platform/01_get_all_components.txt | 1 + .../test/platform/01_get_all_response.json | 1 + .../test/platform/02_get_one_component.txt | 1 + .../02_get_one_component_response.json | 1 + .../03_get_component_attribute_command.txt | 1 + .../03_get_component_attribute_response.json | 1 + .../test/system/01_get_all_components.txt | 1 + .../01_get_all_components_response.json | 1 + .../system/02_get_system_state_command.txt | 1 + .../system/02_get_system_state_response.json | 1 + .../system/03_get_system_memory_command.txt | 1 + .../system/03_get_system_memory_response.json | 1 + .../system/04_get_system_cpus_command.txt | 1 + .../system/04_get_system_cpus_response.json | 1 + .../05_get_system_processes_command.txt | 1 + .../05_get_system_processes_response.json | 1 + .../06_get_system_processes_pid_command.txt | 1 + .../06_get_system_processes_pid_response.json | 1 + ...system_processes_pid_attribute_command.txt | 1 + ...stem_processes_pid_attribute_response.json | 1 + translib/test/translibtest.go | 142 + translib/tlerr/app_errors.go | 92 + translib/tlerr/tlerr.go | 103 + translib/transformer/transformer.go | 161 + translib/transformer/xconst.go | 51 + translib/transformer/xfmr_acl.go | 976 +++++ translib/transformer/xfmr_interface.go | 135 + translib/transformer/xfmr_path_utils.go | 99 + translib/transformer/xlate.go | 407 ++ translib/transformer/xlate_from_db.go | 766 ++++ translib/transformer/xlate_to_db.go | 565 +++ translib/transformer/xlate_utils.go | 591 +++ translib/transformer/xspec.go | 598 +++ translib/translib.go | 745 ++++ translib/translib_test.go | 59 + 217 files changed, 54346 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 config/transformer/models_list create mode 100644 cvl/Makefile create mode 100644 cvl/README.md create mode 100644 cvl/conf/cvl_cfg.json create mode 100644 cvl/cvl.go create mode 100644 cvl/cvl_api.go create mode 100644 cvl/cvl_luascript.go create mode 100644 cvl/cvl_test.go create mode 100644 cvl/internal/util/util.go create mode 100644 cvl/internal/yparser/yparser.go create mode 100644 cvl/jsondata_test.go create mode 100644 cvl/schema/Makefile create mode 100644 cvl/testdata/acl_rule.json create mode 100644 cvl/testdata/aclrule.json create mode 100644 cvl/testdata/acltable.json create mode 100644 cvl/testdata/config_db.json create mode 100644 cvl/testdata/config_db1.json create mode 100644 cvl/testdata/config_db2.json create mode 100644 cvl/testdata/create_acl_table.json create mode 100644 cvl/testdata/create_acl_table12.json create mode 100644 cvl/testdata/create_acl_table13.json create mode 100644 cvl/testdata/port_table.json create mode 100644 cvl/testdata/schema/Makefile create mode 100644 cvl/testdata/schema/sonic-acl-deviation.yang create mode 100644 cvl/testdata/schema/sonic-bgp-neighbor.yang create mode 100644 cvl/testdata/schema/sonic-buffer-pg.yang create mode 100644 cvl/testdata/schema/sonic-buffer-pool.yang create mode 100644 cvl/testdata/schema/sonic-buffer-profile.yang create mode 100644 cvl/testdata/schema/sonic-cablelength.yang create mode 100644 cvl/testdata/schema/sonic-device-metadata.yang create mode 100644 cvl/testdata/schema/sonic-device-neighbor.yang create mode 100644 cvl/testdata/schema/sonic-dscp-tc-map.yang create mode 100644 cvl/testdata/schema/sonic-mirror-session.yang create mode 100644 cvl/testdata/schema/sonic-pf-limits.yang create mode 100644 cvl/testdata/schema/sonic-pfc-priority-queue-map.yang create mode 100644 cvl/testdata/schema/sonic-port-qos-map.yang create mode 100644 cvl/testdata/schema/sonic-portchannel-interface.yang create mode 100644 cvl/testdata/schema/sonic-portchannel.yang create mode 100644 cvl/testdata/schema/sonic-queue.yang create mode 100644 cvl/testdata/schema/sonic-scheduler.yang create mode 100644 cvl/testdata/schema/sonic-tc-priority-group-map.yang create mode 100644 cvl/testdata/schema/sonic-tc-queue-map.yang create mode 100644 cvl/testdata/schema/sonic-vlan-deviation.yang create mode 100644 cvl/testdata/schema/sonic-vlan-interface.yang create mode 100644 cvl/testdata/schema/sonic-vlan.yang create mode 100644 cvl/testdata/schema/sonic-wred-profile.yang create mode 100644 cvl/tests/Makefile create mode 100644 cvl/tests/acl_rule.json create mode 100644 cvl/tests/cfg_validator.go create mode 100644 cvl/tests/config_db.json create mode 100644 cvl/tests/config_db1.json create mode 100644 cvl/tests/config_db2.json create mode 100644 cvl/tests/create_acl_table.json create mode 100644 cvl/tests/cv_acl.go create mode 100644 cvl/tests/cv_edit_op.go create mode 100644 cvl/tests/cv_vlan.go create mode 100755 cvl/tests/run_test.sh create mode 100644 debian/.gitignore create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100755 debian/rules create mode 100644 debian/sonic-mgmt-common-codegen.install create mode 100644 debian/sonic-mgmt-common.install create mode 100644 go.mod create mode 100644 go.sum create mode 100644 models/yang/.gitignore create mode 100644 models/yang/Makefile create mode 100644 models/yang/annotations/openconfig-acl-annot.yang create mode 100644 models/yang/annotations/sonic-extensions.yang create mode 100644 models/yang/common/iana-if-type.yang create mode 100644 models/yang/common/ietf-inet-types.yang create mode 100644 models/yang/common/ietf-interfaces.yang create mode 100644 models/yang/common/ietf-yang-types.yang create mode 100644 models/yang/common/openconfig-aaa-radius.yang create mode 100644 models/yang/common/openconfig-aaa-tacacs.yang create mode 100644 models/yang/common/openconfig-aaa-types.yang create mode 100644 models/yang/common/openconfig-aaa.yang create mode 100644 models/yang/common/openconfig-alarm-types.yang create mode 100644 models/yang/common/openconfig-alarms.yang create mode 100644 models/yang/common/openconfig-extensions.yang create mode 100644 models/yang/common/openconfig-if-aggregate.yang create mode 100644 models/yang/common/openconfig-if-ethernet-ext.yang create mode 100644 models/yang/common/openconfig-if-ip-ext.yang create mode 100644 models/yang/common/openconfig-if-poe.yang create mode 100644 models/yang/common/openconfig-if-tunnel.yang create mode 100644 models/yang/common/openconfig-if-types.yang create mode 100644 models/yang/common/openconfig-inet-types.yang create mode 100644 models/yang/common/openconfig-lldp-types.yang create mode 100644 models/yang/common/openconfig-messages.yang create mode 100644 models/yang/common/openconfig-packet-match-types.yang create mode 100644 models/yang/common/openconfig-packet-match.yang create mode 100644 models/yang/common/openconfig-platform-types.yang create mode 100644 models/yang/common/openconfig-procmon.yang create mode 100644 models/yang/common/openconfig-system-logging.yang create mode 100644 models/yang/common/openconfig-system-management.yang create mode 100644 models/yang/common/openconfig-system-terminal.yang create mode 100644 models/yang/common/openconfig-types.yang create mode 100644 models/yang/common/openconfig-vlan-types.yang create mode 100644 models/yang/common/openconfig-vlan.yang create mode 100644 models/yang/common/openconfig-yang-types.yang create mode 100644 models/yang/extensions/.gitkeep create mode 100644 models/yang/openconfig-acl.yang create mode 100644 models/yang/openconfig-if-ethernet.yang create mode 100644 models/yang/openconfig-if-ip.yang create mode 100644 models/yang/openconfig-interfaces.yang create mode 100644 models/yang/openconfig-lldp.yang create mode 100644 models/yang/openconfig-platform.yang create mode 100644 models/yang/openconfig-system.yang create mode 100644 models/yang/sonic/common/sonic-common.yang create mode 100644 models/yang/sonic/common/sonic-extension.yang create mode 100644 models/yang/sonic/sonic-acl.yang create mode 100644 models/yang/sonic/sonic-interface.yang create mode 100644 models/yang/sonic/sonic-port.yang create mode 100755 patches/apply.sh create mode 100644 patches/glog.patch create mode 100644 patches/goyang/goyang.patch create mode 100644 patches/jsonquery.patch create mode 100644 patches/ygot/ygot.patch create mode 100644 tools/pyang/pyang_plugins/yin_cvl.py create mode 100755 tools/xfmr/annotate.sh create mode 100644 translib/Makefile create mode 100644 translib/acl_app.go create mode 100644 translib/acl_app_test.go create mode 100644 translib/app_interface.go create mode 100644 translib/app_utils.go create mode 100644 translib/common_app.go create mode 100644 translib/db/db.go create mode 100644 translib/db/db_test.go create mode 100644 translib/db/map.go create mode 100644 translib/db/subscribe.go create mode 100644 translib/db/test/arloIssue29.go create mode 100644 translib/db/test/testdb.go create mode 100644 translib/db/test/testmap.go create mode 100644 translib/db/test/testsubscribe.go create mode 100644 translib/intf_app.go create mode 100644 translib/lldp_app.go create mode 100644 translib/nonyang_app.go.demo create mode 100644 translib/ocbinds/oc.go create mode 100644 translib/path_utils.go create mode 100644 translib/path_utils_test.go create mode 100644 translib/pfm_app.go create mode 100644 translib/request_binder.go create mode 100644 translib/request_binder_test.go create mode 100644 translib/subscribe.go create mode 100644 translib/sys_app.go create mode 100644 translib/test/acl/01_create_MyACL1_MyACL2.json create mode 100644 translib/test/acl/02_create_MyACL3_5Rules.json create mode 100644 translib/test/acl/03_create_MyACL1_5_more_rules.json create mode 100644 translib/test/acl/04_create_MyACL1_1Rule_content.json create mode 100644 translib/test/acl/05_Create_MyACL3_binding.json create mode 100644 translib/test/acl/08_update_AclSets_MyACL3.json create mode 100644 translib/test/acl/09_after_delete_1Rule_get_MyACL4_command.txt create mode 100644 translib/test/acl/09_after_delete_1Rule_get_MyACL4_response.json create mode 100644 translib/test/acl/09_create_MyACL4_4Rules.json create mode 100644 translib/test/acl/09_create_MyACL4_4Rules_command.txt create mode 100644 translib/test/acl/09_delete_1Rule_MyACL4_command.txt create mode 100644 translib/test/acl/09_get_1Rule_MyACL4_command.txt create mode 100644 translib/test/acl/09_get_all_MyACL4_4Rules_command.txt create mode 100644 translib/test/acl/09_get_all_MyACL4_4Rules_response.json create mode 100644 translib/test/interfaces/01_get_all_command.txt create mode 100644 translib/test/interfaces/01_get_all_response.json create mode 100644 translib/test/interfaces/02_get_interface_command.txt create mode 100644 translib/test/interfaces/02_get_interface_response.json create mode 100644 translib/test/interfaces/03_get_interface_config_command.txt create mode 100644 translib/test/interfaces/03_get_interface_config_response.json create mode 100644 translib/test/interfaces/04_get_interface_state_command.txt create mode 100644 translib/test/interfaces/04_get_interface_state_response.json create mode 100644 translib/test/interfaces/05_set_interface_mtu.json create mode 100644 translib/test/interfaces/05_set_interface_mtu_command.txt create mode 100644 translib/test/interfaces/06_set_interface_ipv4.json create mode 100644 translib/test/interfaces/06_set_interface_ipv4_command.txt create mode 100644 translib/test/interfaces/07_set_interface_ipv6.json create mode 100644 translib/test/interfaces/07_set_interface_ipv6_command.txt create mode 100644 translib/test/interfaces/08_delete_interface_ipv4_command.txt create mode 100644 translib/test/interfaces/09_delete_interface_ipv6_command.txt create mode 100644 translib/test/lldp/01-get-all-intf-command.txt create mode 100644 translib/test/lldp/01-get-all-intf-response.json create mode 100644 translib/test/lldp/02-get-one-intf-command.txt create mode 100644 translib/test/lldp/02-get-one-intf-response.json create mode 100644 translib/test/platform/01_get_all_components.txt create mode 100644 translib/test/platform/01_get_all_response.json create mode 100644 translib/test/platform/02_get_one_component.txt create mode 100644 translib/test/platform/02_get_one_component_response.json create mode 100644 translib/test/platform/03_get_component_attribute_command.txt create mode 100644 translib/test/platform/03_get_component_attribute_response.json create mode 100644 translib/test/system/01_get_all_components.txt create mode 100644 translib/test/system/01_get_all_components_response.json create mode 100644 translib/test/system/02_get_system_state_command.txt create mode 100644 translib/test/system/02_get_system_state_response.json create mode 100644 translib/test/system/03_get_system_memory_command.txt create mode 100644 translib/test/system/03_get_system_memory_response.json create mode 100644 translib/test/system/04_get_system_cpus_command.txt create mode 100644 translib/test/system/04_get_system_cpus_response.json create mode 100644 translib/test/system/05_get_system_processes_command.txt create mode 100644 translib/test/system/05_get_system_processes_response.json create mode 100644 translib/test/system/06_get_system_processes_pid_command.txt create mode 100644 translib/test/system/06_get_system_processes_pid_response.json create mode 100644 translib/test/system/07_get_system_processes_pid_attribute_command.txt create mode 100644 translib/test/system/07_get_system_processes_pid_attribute_response.json create mode 100644 translib/test/translibtest.go create mode 100644 translib/tlerr/app_errors.go create mode 100644 translib/tlerr/tlerr.go create mode 100644 translib/transformer/transformer.go create mode 100644 translib/transformer/xconst.go create mode 100644 translib/transformer/xfmr_acl.go create mode 100644 translib/transformer/xfmr_interface.go create mode 100644 translib/transformer/xfmr_path_utils.go create mode 100644 translib/transformer/xlate.go create mode 100644 translib/transformer/xlate_from_db.go create mode 100644 translib/transformer/xlate_to_db.go create mode 100644 translib/transformer/xlate_utils.go create mode 100644 translib/transformer/xspec.go create mode 100644 translib/translib.go create mode 100644 translib/translib_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ff8d5e69d --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.vscode +*.code-workspace +build +vendor +__pycache__ +*.pyc +*.rdb +*.swp +*.yin +*.tree +translib/ocbinds/ocbinds.go diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..d1b0bf18b --- /dev/null +++ b/Makefile @@ -0,0 +1,89 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +TOPDIR := $(abspath .) +BUILD_DIR := build + +GOPATH ?= /tmp/go +GO ?= /usr/local/go/bin/go + +GO_MOD = go.mod +GO_DEPS = vendor/.done +GO_PATCHES = $(shell find patches -type f) +GOYANG_BIN = $(abspath $(BUILD_DIR)/bin/goyang) + +export TOPDIR GO GOPATH + +all: models cvl translib + +$(GO_MOD): + $(GO) mod init github.com/Azure/sonic-mgmt-common + +$(GO_DEPS): $(GO_MOD) $(GO_PATCHES) + $(GO) mod vendor + patches/apply.sh vendor + touch $@ + +go-deps: $(GO_DEPS) + +go-deps-clean: + $(RM) -r vendor + +.PHONY: cvl +cvl: $(GO_DEPS) + $(MAKE) -C ./cvl + +cvl-all: $(GO_DEPS) + $(MAKE) -C ./cvl all + +cvl-clean: + $(MAKE) -C ./cvl clean + +cvl-test: + $(MAKE) -C ./cvl gotest + +.PHONY: translib +translib: $(GO_DEPS) + $(MAKE) -C ./translib + +translib-all: $(GO_DEPS) + $(MAKE) -C ./translib all + +translib-clean: + $(MAKE) -C ./translib clean + +.PHONY: models +models: + $(MAKE) -C models/yang + +models-clean: + $(MAKE) -C models/yang clean + +annotgen: $(GOYANG_BIN) + +$(GOYANG_BIN): $(GO_DEPS) + cd vendor/github.com/openconfig/goyang && \ + $(GO) build -o $@ *.go + +clean: models-clean translib-clean cvl-clean + git check-ignore debian/* | xargs -r $(RM) -r + $(RM) -r $(BUILD_DIR) + +cleanall: clean go-deps-clean + git clean -fdX tools diff --git a/config/transformer/models_list b/config/transformer/models_list new file mode 100644 index 000000000..c9f26c163 --- /dev/null +++ b/config/transformer/models_list @@ -0,0 +1,3 @@ +#List yang models transformer need to load +openconfig-acl.yang +openconfig-acl-annot.yang diff --git a/cvl/Makefile b/cvl/Makefile new file mode 100644 index 000000000..f2786e5ba --- /dev/null +++ b/cvl/Makefile @@ -0,0 +1,70 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +GO?=go +SRC_FILES=$(shell find . -name '*.go' | grep -v '_test.go' | grep -v '/tests/') +TEST_FILES=$(wildcard *_test.go) +TOP_DIR := .. +BUILD_DIR:=$(TOP_DIR)/build/cvl +CVL_PKG=$(TOP_DIR)/build/pkg/linux_amd64/cvl.a + +CVL_TEST_DIR = $(TOP_DIR)/build/tests/cvl +CVL_TEST_BIN = $(CVL_TEST_DIR)/cvl.test +CVL_TEST_SCHEMA_DIR = $(CVL_TEST_DIR)/testdata/schema + +DEFAULT_TARGETS = deps schema +ifeq ($(NO_TEST_BINS),) +DEFAULT_TARGETS += $(CVL_TEST_BIN) test-schema +endif + +default: $(DEFAULT_TARGETS) + +all: $(DEFAULT_TARGETS) $(CVL_PKG) + +deps: + mkdir -p $(BUILD_DIR) + +$(CVL_PKG): $(SRC_FILES) + @echo "Building $@" + $(GO) build -mod=vendor -o $(CVL_PKG) ../cvl + +$(CVL_TEST_BIN): $(TEST_FILES) $(SRC_FILES) | test-schema + cp -r testdata/*.json $(@D)/testdata + $(GO) test -mod=vendor -cover -coverpkg=../cvl,../cvl/internal/util,../cvl/internal/yparser -c ../cvl -o $@ + +.PHONY: schema +schema: + $(MAKE) -C schema + +test-schema: + $(MAKE) -C testdata/schema + +tests: + $(MAKE) -C tests + +gotest:schema test-schema + CVL_CFG_FILE=$(abspath .)/conf/cvl_cfg.json CVL_SCHEMA_PATH=$(CVL_TEST_SCHEMA_DIR) tests/run_test.sh + +clean: + $(MAKE) -C tests clean + $(MAKE) -C schema clean + $(MAKE) -C testdata/schema clean + $(RM) -r $(CVL_PKG) + $(RM) -r $(CVL_TEST_DIR) + diff --git a/cvl/README.md b/cvl/README.md new file mode 100644 index 000000000..9445cb22a --- /dev/null +++ b/cvl/README.md @@ -0,0 +1,62 @@ +1. Install latest version of pyang tool. + +2. Install libyang from https://github.com/CESNET/libyang along with its dependency. + +3. Run 'make' from top level 'cvl' directory. + +4. Refer to top level makefile rules for compiling individual targets. + +5. 'schema' directory should contain all .yin files + +6. On the target the 'schema' directory needs to be present in the same directory where application executable file is present. + + +Debugging Info: +=============== + +Below steps need to be done to enable CVL logging. + +1. Find the CVL json config file in mgmt-framework docker in switch at "/usr/sbin/cvl_cfg.json" . + +2. Change the logging flags from "false" to "true" as below: + + { + "TRACE_CACHE": "true", + "TRACE_LIBYANG": "true", + "TRACE_YPARSER": "true", + "TRACE_CREATE": "true", + "TRACE_UPDATE": "true", + "TRACE_DELETE": "true", + "TRACE_SEMANTIC": "true", + "TRACE_SYNTAX": "true", + "__comment1__": "Set LOGTOSTDER to 'true' to log on standard error", + "LOGTOSTDERR": "true", + "__comment2__": "Display log upto INFO level", + "STDERRTHRESHOLD": "INFO", + "__comment3__": "Display log upto INFO level 8", + "VERBOSITY": "8", + "SKIP_VALIDATION": "false", + "SKIP_SEMANTIC_VALIDATION": "false" + } +3. Below environment variables need to be set at the end in /usr/bin/rest-server.sh in mgmt-framework docker. + + export CVL_DEBUG=1 + export CVL_CFG_FILE=/usr/sbin/cvl_cfg.json + + Note : CVL_CFG_FILE enviroment variable can point to other location also. + +4. CVL Traces can be enabled both with restart and without mgmt-framework docker restart . + + With Restart: + ============ + Restart mgmt-framework docker after which updated cvl_cfg.json file will be read. + + Without Restart: + =============== + Issue SIGUSR2 to rest process(kill -SIGUSR2 , to read changed cvl_cfg.json with logging enabled. + +5. After following above steps, CVL traces can be seen in syslog file in host container at /var/log/syslog. + +6. To disable CVL traces , disable the fields in cvl_cfg.json file and then perform same steps as in Step 4. + + diff --git a/cvl/conf/cvl_cfg.json b/cvl/conf/cvl_cfg.json new file mode 100644 index 000000000..1445bf302 --- /dev/null +++ b/cvl/conf/cvl_cfg.json @@ -0,0 +1,20 @@ +{ + "TRACE_CACHE": "false", + "TRACE_LIBYANG": "false", + "TRACE_YPARSER": "false", + "TRACE_CREATE": "false", + "TRACE_UPDATE": "false", + "TRACE_DELETE": "false", + "TRACE_SEMANTIC": "false", + "TRACE_SYNTAX": "false", + "__comment1__": "Log trace data when error occurs", + "TRACE_ONERROR": "true", + "__comment2__": "Set LOGTOSTDER to 'true' to log on standard error", + "LOGTOSTDERR": "false", + "__comment3__": "Display log upto INFO level", + "STDERRTHRESHOLD": "ERROR", + "__comment4__": "Display log upto INFO level 8", + "VERBOSITY": "0", + "SKIP_VALIDATION": "false", + "SKIP_SEMANTIC_VALIDATION": "false" +} diff --git a/cvl/cvl.go b/cvl/cvl.go new file mode 100644 index 000000000..858aa08fa --- /dev/null +++ b/cvl/cvl.go @@ -0,0 +1,1864 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package cvl +import ( + "fmt" + "os" + "strings" + "regexp" + "time" + log "github.com/golang/glog" + "encoding/json" + "github.com/go-redis/redis" + "github.com/antchfx/xmlquery" + "github.com/antchfx/jsonquery" + "github.com/Azure/sonic-mgmt-common/cvl/internal/yparser" + . "github.com/Azure/sonic-mgmt-common/cvl/internal/util" + "sync" + "flag" + "runtime" +) + +//DB number +const ( + APPL_DB uint8 = 0 + iota + ASIC_DB + COUNTERS_DB + LOGLEVEL_DB + CONFIG_DB + PFC_WD_DB + FLEX_COUNTER_DB = PFC_WD_DB + STATE_DB + SNMP_OVERLAY_DB + INVALID_DB +) + +const DEFAULT_CACHE_DURATION uint16 = 300 /* 300 sec */ +const MAX_BULK_ENTRIES_IN_PIPELINE int = 50 + +var reLeafRef *regexp.Regexp = nil +var reHashRef *regexp.Regexp = nil +var reSelKeyVal *regexp.Regexp = nil +var reLeafInXpath *regexp.Regexp = nil + +var cvlInitialized bool +var dbNameToDbNum map[string]uint8 + +//map of lua script loaded +var luaScripts map[string]*redis.Script + +//var tmpDbCache map[string]interface{} //map of table storing map of key-value pair + //m["PORT_TABLE] = {"key" : {"f1": "v1"}} +//Important schema information to be loaded at bootup time +type modelTableInfo struct { + dbNum uint8 + modelName string + redisTableName string //To which Redis table it belongs to, used for 1 Redis to N Yang List + module *yparser.YParserModule + keys []string + redisKeyDelim string + redisKeyPattern string + mapLeaf []string //for 'mapping list' + leafRef map[string][]string //for storing all leafrefs for a leaf in a table, + //multiple leafref possible for union + mustExp map[string]string + tablesForMustExp map[string]CVLOperation +} + + +/* CVL Error Structure. */ +type CVLErrorInfo struct { + TableName string /* Table having error */ + ErrCode CVLRetCode /* CVL Error return Code. */ + CVLErrDetails string /* CVL Error Message details. */ + Keys []string /* Keys of the Table having error. */ + Value string /* Field Value throwing error */ + Field string /* Field Name throwing error . */ + Msg string /* Detailed error message. */ + ConstraintErrMsg string /* Constraint error message. */ + ErrAppTag string +} + +type CVL struct { + redisClient *redis.Client + yp *yparser.YParser + tmpDbCache map[string]interface{} //map of table storing map of key-value pair + requestCache map[string]map[string][]CVLEditConfigData //Cache of validated data, + //might be used as dependent data in next request + batchLeaf string + chkLeafRefWithOthCache bool +} + +type modelNamespace struct { + prefix string + ns string +} + +type modelDataInfo struct { + modelNs map[string]modelNamespace //model namespace + tableInfo map[string]*modelTableInfo //redis table to model name and keys + redisTableToYangList map[string][]string //Redis table to all YANG lists when it is not 1:1 mapping + allKeyDelims map[string]bool +} + +//Struct for storing global DB cache to store DB which are needed frequently like PORT +type dbCachedData struct { + root *yparser.YParserNode //Root of the cached data + startTime time.Time //When cache started + expiry uint16 //How long cache should be maintained in sec +} + +//Global data cache for redis table +type cvlGlobalSessionType struct { + db map[string]dbCachedData + pubsub *redis.PubSub + stopChan chan int //stop channel to stop notification listener + cv *CVL + mutex *sync.Mutex +} +var cvg cvlGlobalSessionType + +//Single redis client for validation +var redisClient *redis.Client + +//Stores important model info +var modelInfo modelDataInfo + +type keyValuePairStruct struct { + key string + values []string +} + +func TRACE_LOG(level log.Level, tracelevel CVLTraceLevel, fmtStr string, args ...interface{}) { + TRACE_LEVEL_LOG(level, tracelevel, fmtStr, args...) +} + +func CVL_LOG(level CVLLogLevel, fmtStr string, args ...interface{}) { + CVL_LEVEL_LOG(level, fmtStr, args...) +} + +//package init function +func init() { + if (os.Getenv("CVL_SCHEMA_PATH") != "") { + CVL_SCHEMA = os.Getenv("CVL_SCHEMA_PATH") + "/" + } + + if (os.Getenv("CVL_DEBUG") != "") { + SetTrace(true) + } + + ConfigFileSyncHandler() + + cvlCfgMap := ReadConfFile() + + if (cvlCfgMap != nil) { + if (strings.Compare(cvlCfgMap["LOGTOSTDERR"], "true") == 0) { + flag.Set("logtostderr", "true") + flag.Set("stderrthreshold", cvlCfgMap["STDERRTHRESHOLD"]) + flag.Set("v", cvlCfgMap["VERBOSITY"]) + } + + CVL_LOG(INFO ,"Current Values of CVL Configuration File %v", cvlCfgMap) + } + + //regular expression for leafref and hashref finding + reLeafRef = regexp.MustCompile(`.*[/]([a-zA-Z]*:)?(.*)[/]([a-zA-Z]*:)?(.*)`) + reHashRef = regexp.MustCompile(`\[(.*)\|(.*)\]`) + reSelKeyVal = regexp.MustCompile("=[ ]*['\"]?([0-9_a-zA-Z]+)['\"]?|(current[(][)])") + reLeafInXpath = regexp.MustCompile("(.*[:/]{1})([a-zA-Z0-9_-]+)([^a-zA-Z0-9_-]*)") + + Initialize() + + cvg.db = make(map[string]dbCachedData) + + //Global session keeps the global cache + cvg.cv, _ = ValidationSessOpen() + //Create buffer channel of length 1 + cvg.stopChan = make(chan int, 1) + //Initialize mutex + cvg.mutex = &sync.Mutex{} + + _, err := redisClient.ConfigSet("notify-keyspace-events", "AKE").Result() + if err != nil { + CVL_LOG(ERROR ,"Could not enable notification error %s", err) + } + + dbCacheSet(false, "PORT", 0) +} + +func Debug(on bool) { + yparser.Debug(on) +} + +//Get attribute value of xml node +func getXmlNodeAttr(node *xmlquery.Node, attrName string) string { + for _, attr := range node.Attr { + if (attrName == attr.Name.Local) { + return attr.Value + } + } + + return "" +} + +//Store useful schema data during initialization +func storeModelInfo(modelFile string, module *yparser.YParserModule) { //such model info can be maintained in C code and fetched from there + f, err := os.Open(CVL_SCHEMA + modelFile) + root, err := xmlquery.Parse(f) + + if err != nil { + return + } + f.Close() + + //model is derived from file name + tokens := strings.Split(modelFile, ".") + modelName := tokens[0] + + //Store namespace + modelNs := modelNamespace{} + + nodes := xmlquery.Find(root, "//module/namespace") + if (nodes != nil) { + modelNs.ns = nodes[0].Attr[0].Value + } + + nodes = xmlquery.Find(root, "//module/prefix") + if (nodes != nil) { + modelNs.prefix = nodes[0].Attr[0].Value + } + + modelInfo.modelNs[modelName] = modelNs + + //Store metadata present in each list. + //Each list represent one Redis table in general. + //However when one Redis table is mapped to multiple + //YANG lists need to store the information in redisTableToYangList map + nodes = xmlquery.Find(root, "//module/container/container/list") + if (nodes == nil) { + return + } + + //number list under one table container i.e. ACL_TABLE container + //has only one ACL_TABLE_LIST list + for _, node := range nodes { + //for each list, remove "_LIST" suffix + tableName := node.Attr[0].Value + if (strings.HasSuffix(tableName, "_LIST")) { + tableName = tableName[0:len(tableName) - len("_LIST")] + } + tableInfo := modelTableInfo{modelName: modelName} + //Store Redis table name + tableInfo.redisTableName = node.Parent.Attr[0].Value + //Store the reference for list node to be used later + listNode := node + node = node.FirstChild + //Default database is CONFIG_DB since CVL works with config db mainly + tableInfo.module = module + tableInfo.dbNum = CONFIG_DB + //default delim '|' + tableInfo.redisKeyDelim = "|" + modelInfo.allKeyDelims[tableInfo.redisKeyDelim] = true + + fieldCount := 0 + + //Check for meta data in schema + for node != nil { + switch node.Data { + case "db-name": + tableInfo.dbNum = dbNameToDbNum[node.Attr[0].Value] + fieldCount++ + case "key": + tableInfo.keys = strings.Split(node.Attr[0].Value," ") + fieldCount++ + keypattern := []string{tableName} + + /* Create the default key pattern of the form Table Name|{key1}|{key2}. */ + for _ , key := range tableInfo.keys { + keypattern = append(keypattern, fmt.Sprintf("{%s}",key)) + } + + tableInfo.redisKeyPattern = strings.Join(keypattern, tableInfo.redisKeyDelim) + + case "key-delim": + tableInfo.redisKeyDelim = node.Attr[0].Value + fieldCount++ + //store all possible key delims + modelInfo.allKeyDelims[tableInfo.redisKeyDelim] = true + case "key-pattern": + tableInfo.redisKeyPattern = node.Attr[0].Value + fieldCount++ + case "map-leaf": + tableInfo.mapLeaf = strings.Split(node.Attr[0].Value," ") + fieldCount++ + } + node = node.NextSibling + } + + //Find and store all leafref under each table + /* + if (listNode == nil) { + //Store the tableInfo in global data + modelInfo.tableInfo[tableName] = tableInfo + + continue + } + */ + + //If container has more than one list, it means one Redis table is mapped to + //multiple lists, store the info in redisTableToYangList + allLists := xmlquery.Find(listNode.Parent, "/list") + if len(allLists) > 1 { + yangList := modelInfo.redisTableToYangList[tableInfo.redisTableName] + yangList = append(yangList, tableName) + //Update the map + modelInfo.redisTableToYangList[tableInfo.redisTableName] = yangList + } + + leafRefNodes := xmlquery.Find(listNode, "//type[@name='leafref']") + if (leafRefNodes == nil) { + //Store the tableInfo in global data + modelInfo.tableInfo[tableName] = &tableInfo + + continue + } + + tableInfo.leafRef = make(map[string][]string) + for _, leafRefNode := range leafRefNodes { + if (leafRefNode.Parent == nil || leafRefNode.FirstChild == nil) { + continue + } + + //Get the leaf/leaf-list name holding this leafref + //Note that leaf can have union of leafrefs + leafName := "" + for node := leafRefNode.Parent; node != nil; node = node.Parent { + if (node.Data == "leaf" || node.Data == "leaf-list") { + leafName = getXmlNodeAttr(node, "name") + break + } + } + + //Store the leafref path + if (leafName != "") { + tableInfo.leafRef[leafName] = append(tableInfo.leafRef[leafName], + getXmlNodeAttr(leafRefNode.FirstChild, "value")) + } + } + + //Find all 'must' expression and store the against its parent node + mustExps := xmlquery.Find(listNode, "//must") + if (mustExps == nil) { + //Update the tableInfo in global data + modelInfo.tableInfo[tableName] = &tableInfo + continue + } + + tableInfo.mustExp = make(map[string]string) + for _, mustExp := range mustExps { + if (mustExp.Parent == nil) { + continue + } + parentName := "" + for node := mustExp.Parent; node != nil; node = node.Parent { + //assuming must exp is at leaf or list level + if (node.Data == "leaf" || node.Data == "leaf-list" || + node.Data == "list") { + parentName = getXmlNodeAttr(node, "name") + break + } + } + if (parentName != "") { + tableInfo.mustExp[parentName] = getXmlNodeAttr(mustExp, "condition") + } + } + + //Update the tableInfo in global data + modelInfo.tableInfo[tableName] = &tableInfo + + } +} + +//Find the tables names in must expression, these tables data need to be fetched +//during semantic validation +func addTableNamesForMustExp() { + + for tblName, tblInfo := range modelInfo.tableInfo { + if (tblInfo.mustExp == nil) { + continue + } + + tblInfo.tablesForMustExp = make(map[string]CVLOperation) + + for _, mustExp := range tblInfo.mustExp { + var op CVLOperation = OP_NONE + //Check if 'must' expression should be executed for a particular operation + if (strings.Contains(mustExp, + "/scommon:operation/scommon:operation != CREATE") == true) { + op = op | OP_CREATE + } else if (strings.Contains(mustExp, + "/scommon:operation/scommon:operation != UPDATE") == true) { + op = op | OP_UPDATE + } else if (strings.Contains(mustExp, + "/scommon:operation/scommon:operation != DELETE") == true) { + op = op | OP_DELETE + } + + //store the current table if aggregate function like count() is used + /*if (strings.Contains(mustExp, "count") == true) { + tblInfo.tablesForMustExp[tblName] = op + }*/ + + //check which table name is present in the must expression + for tblNameSrch, _ := range modelInfo.tableInfo { + if (tblNameSrch == tblName) { + continue + } + //Table name should appear like "../VLAN_MEMBER/tagging_mode' or ' + // "/prt:PORT/prt:ifname" + re := regexp.MustCompile(fmt.Sprintf(".*[/]([a-zA-Z]*:)?%s[\\[/]", tblNameSrch)) + matches := re.FindStringSubmatch(mustExp) + if (len(matches) > 0) { + //stores the table name + tblInfo.tablesForMustExp[tblNameSrch] = op + } + } + } + + //update map + modelInfo.tableInfo[tblName] = tblInfo + } +} + +//Split key into table prefix and key +func splitRedisKey(key string) (string, string) { + + var foundIdx int = -1 + //Check with all key delim + for keyDelim, _ := range modelInfo.allKeyDelims { + foundIdx = strings.Index(key, keyDelim) + if (foundIdx >= 0) { + //Matched with key delim + break + } + } + + if (foundIdx < 0) { + //No matches + return "", "" + } + + tblName := key[:foundIdx] + + if _, exists := modelInfo.tableInfo[tblName]; exists == false { + //Wrong table name + return "", "" + } + + prefixLen := foundIdx + 1 + + return tblName, key[prefixLen:] +} + +//Get the YANG list name from Redis key +//This just returns same YANG list name as Redis table name +//when 1:1 mapping is there. For one Redis table to +//multiple YANG list, it returns appropriate YANG list name +//INTERFACE:Ethernet12 returns ==> INTERFACE +//INTERFACE:Ethernet12:1.1.1.0/32 ==> INTERFACE_IPADDR +func getRedisKeyToYangList(tableName, key string) string { + mapArr, exists := modelInfo.redisTableToYangList[tableName] + + if exists == false { + //1:1 mapping case + return tableName + } + + //As of now determine the mapping based on number of keys + var foundIdx int = -1 + numOfKeys := 1 //Assume only one key initially + for keyDelim, _ := range modelInfo.allKeyDelims { + foundIdx = strings.Index(key, keyDelim) + if (foundIdx >= 0) { + //Matched with key delim + keyComps := strings.Split(key, keyDelim) + numOfKeys = len(keyComps) + break + } + } + + //Check which list has number of keys as 'numOfKeys' + for i := 0; i < len(mapArr); i++ { + tblInfo, exists := modelInfo.tableInfo[mapArr[i]] + if exists == true { + if (len(tblInfo.keys) == numOfKeys) { + //Found the YANG list matching the number of keys + return mapArr[i] + } + } + } + + //No matches + return tableName +} + +//Convert Redis key to Yang keys, if multiple key components are there, +//they are separated based on Yang schema +func getRedisToYangKeys(tableName string, redisKey string)[]keyValuePairStruct{ + keyNames := modelInfo.tableInfo[tableName].keys + //First split all the keys components + keyVals := strings.Split(redisKey, modelInfo.tableInfo[tableName].redisKeyDelim) //split by DB separator + //Store patterns for each key components by splitting using key delim + keyPatterns := strings.Split(modelInfo.tableInfo[tableName].redisKeyPattern, + modelInfo.tableInfo[tableName].redisKeyDelim) //split by DB separator + + /* TBD. Workaround for optional keys in INTERFACE Table. + Code will be removed once model is finalized. */ + if ((tableName == "INTERFACE") && (len(keyNames) != len(keyVals))) { + keyVals = append(keyVals, "0.0.0.0/0") + + } else if (len(keyNames) != len(keyVals)) { + return nil //number key names and values does not match + } + + mkeys := []keyValuePairStruct{} + //For each key check the pattern and store key/value pair accordingly + for idx, keyName := range keyNames { + + //check if key-pattern contains specific key pattern + if (keyPatterns[idx+1] == fmt.Sprintf("({%s},)*", keyName)) { // key pattern is "({key},)*" i.e. repeating keys seperated by ',' + repeatedKeys := strings.Split(keyVals[idx], ",") + mkeys = append(mkeys, keyValuePairStruct{keyName, repeatedKeys}) + + } else if (keyPatterns[idx+1] == fmt.Sprintf("{%s}", keyName)) { //no specific key pattern - just "{key}" + + //Store key/value mapping + mkeys = append(mkeys, keyValuePairStruct{keyName, []string{keyVals[idx]}}) + } + } + + return mkeys +} + + +//Add child node to a parent node +func(c *CVL) addChildNode(tableName string, parent *yparser.YParserNode, name string) *yparser.YParserNode { + + //return C.lyd_new(parent, modelInfo.tableInfo[tableName].module, C.CString(name)) + return c.yp.AddChildNode(modelInfo.tableInfo[tableName].module, parent, name) +} + +//Check for path resolution +func (c *CVL) checkPathForTableEntry(tableName string, currentValue string, cfgData *CVLEditConfigData, mustExpStk []string, token string) ([]string, string, CVLRetCode) { + + n := len(mustExpStk) - 1 + xpath := "" + + if (token == ")") { + for n = len(mustExpStk) - 1; mustExpStk[n] != "("; n = len(mustExpStk) - 1 { + //pop until "(" + xpath = mustExpStk[n] + xpath + mustExpStk = mustExpStk[:n] + } + } else if (token == "]") { + //pop until "[" + for n = len(mustExpStk) - 1; mustExpStk[n] != "["; n = len(mustExpStk) - 1 { + xpath = mustExpStk[n] + xpath + mustExpStk = mustExpStk[:n] + } + } + + mustExpStk = mustExpStk[:n] + targetTbl := "" + //Search the table name in xpath + for tblNameSrch, _ := range modelInfo.tableInfo { + if (tblNameSrch == tableName) { + continue + } + //Table name should appear like "../VLAN_MEMBER/tagging_mode' or ' + // "/prt:PORT/prt:ifname" + //re := regexp.MustCompile(fmt.Sprintf(".*[/]([a-zA-Z]*:)?%s[\\[/]", tblNameSrch)) + tblSrchIdx := strings.Index(xpath, fmt.Sprintf("/%s_LIST", tblNameSrch)) //no preifx + if (tblSrchIdx < 0) { + tblSrchIdx = strings.Index(xpath, fmt.Sprintf(":%s_LIST", tblNameSrch)) //with prefix + } + if (tblSrchIdx < 0) { + continue + } + + tblSrchIdxEnd := strings.Index(xpath[tblSrchIdx+len(tblNameSrch)+1:], "[") + if (tblSrchIdxEnd < 0) { + tblSrchIdxEnd = strings.Index(xpath[tblSrchIdx+len(tblNameSrch)+1:], "/") + } + + if (tblSrchIdxEnd >= 0) { //match found + targetTbl = tblNameSrch + break + } + } + + //No match with table found, could be just keys like 'aclname='TestACL1' + //just return the same + if (targetTbl == "") { + return mustExpStk, xpath, CVL_SUCCESS + } + + tableKey := targetTbl + //Add the keys + keyNames := modelInfo.tableInfo[tableKey].keys + + //Form the Redis Key to fetch the entry + for idx, keyName := range keyNames { + //Key value is string/numeric literal, extract the same + keySrchIdx := strings.Index(xpath, keyName) + if (keySrchIdx < 0 ) { + continue + } + + matches := reSelKeyVal.FindStringSubmatch(xpath[keySrchIdx+len(keyName):]) + if (len(matches) > 1) { + if (matches[1] == "current()") { + //replace with current field value + tableKey = tableKey + "*" + modelInfo.tableInfo[tableName].redisKeyDelim + currentValue + } else { + //Use literal + tableKey = tableKey + "*" + modelInfo.tableInfo[tableName].redisKeyDelim + matches[1] + } + + if (idx != len(keyNames) - 1) { + tableKey = tableKey + "|*" + } + } + } + + //Fetch the entries + redisTableKeys, err:= redisClient.Keys(tableKey).Result() + + if (err !=nil || len (redisTableKeys) > 1) { //more than one entry is returned, can't proceed further + //Just add all the entries for caching + for _, redisTableKey := range redisTableKeys { + c.addTableEntryToCache(splitRedisKey(redisTableKey)) + } + + return mustExpStk, "", CVL_SUCCESS + } + + for _, redisTableKey := range redisTableKeys { + + var entry map[string]string + + if tmpEntry, mergeNeeded := c.fetchDataFromRequestCache(splitRedisKey(redisTableKey)); (tmpEntry == nil || mergeNeeded == true) { + //If data is not available in validated cache fetch from Redis DB + entry, err = redisClient.HGetAll(redisTableKey).Result() + + if (mergeNeeded) { + mergeMap(entry, tmpEntry) + } + } + + //Get the entry fields from Redis + if (entry != nil) { + //Just add all the entries for caching + c.addTableEntryToCache(splitRedisKey(redisTableKey)) + + leafInPath := "" + index := strings.LastIndex(xpath, "/") + if (index >= 0) { + + matches := reLeafInXpath.FindStringSubmatch(xpath[index:]) + if (len(matches) > 2) { //should return atleasts two subgroup and entire match + leafInPath = matches[2] + } else { + //No leaf requested in xpath selection + return mustExpStk, "", CVL_SUCCESS + } + + index = strings.Index(xpath, "=") + tblIndex := strings.Index(xpath, targetTbl) + if (index >= 0 && tblIndex >=0) { + + if leafVal, existing := entry[leafInPath + "@"]; existing == true { + //Get the field value referred in the xpath + if (index < tblIndex) { + // case like - [ifname=../../ACL_TABLE[aclname=current()] + return mustExpStk, xpath[:index+1] + leafVal, CVL_SUCCESS + } else { + // case like - [ifname=current()] + return mustExpStk, leafVal, CVL_SUCCESS + } + } else { + + if (index < tblIndex) { + return mustExpStk, xpath[:index+1] + entry[leafInPath], CVL_SUCCESS + } else { + return mustExpStk, entry[leafInPath], CVL_SUCCESS + } + } + } + } + } + } + + return mustExpStk, "", CVL_FAILURE +} + +//Add specific entries by looking at must expression +//Must expression may need single or multiple entries +//It can be within same table or across multiple tables +//Node-set function such count() can be quite expensive and +//should be avoided through this function +func (c *CVL) addTableEntryForMustExp(cfgData *CVLEditConfigData, tableName string) CVLRetCode { + if (modelInfo.tableInfo[tableName].mustExp == nil) { + return CVL_SUCCESS + } + + for fieldName, mustExp := range modelInfo.tableInfo[tableName].mustExp { + + currentValue := "" // Current value for current() function + + //Get the current() field value from the entry being created/updated/deleted + keyValuePair := getRedisToYangKeys(tableName, cfgData.Key[len(tableName)+1:]) + + //Try to get the current() from the 'key' provided + if (keyValuePair != nil) { + for _, keyValItem := range keyValuePair { + if (keyValItem.key == fieldName) { + currentValue = keyValItem.values[0] + } + } + } + + //current() value needs to be fetched from other field + if (currentValue == "") { + if (cfgData.VOp == OP_CREATE) { + if (tableName != fieldName) { //must expression is not at list level + currentValue = cfgData.Data[fieldName] + if (currentValue == "") { + currentValue = cfgData.Data[fieldName + "@"] + } + } + } else if (cfgData.VOp == OP_UPDATE || cfgData.VOp == OP_DELETE) { + //fetch the entry to get current() value + c.clearTmpDbCache() + entryKey := cfgData.Key[len(tableName)+1:] + c.tmpDbCache[tableName] = map[string]interface{}{entryKey: nil} + + if (c.fetchTableDataToTmpCache(tableName, + map[string]interface{}{entryKey: nil}) > 0) { + mapTable := c.tmpDbCache[tableName] + if fields, existing := mapTable.(map[string]interface{})[entryKey]; existing == true { + currentValue = fmt.Sprintf("%v", fields.(map[string]interface{})[fieldName]) + } + } + } + } + + mustExpStk := []string{} //Use the string slice as stack + mustExpStr := "(" + mustExp + ")" + strLen := len(mustExpStr) + strTmp := "" + //Parse the xpath expression and fetch Redis entry by looking at xpath, + // any xpath function call is ignored except current(). + for i := 0; i < strLen; i++ { + switch mustExpStr[i] { + case '(': + if (mustExpStr[i+1] == ')') { + strTmp = strTmp + "()" + if index := strings.Index(strTmp, "current()"); index >= 0 { + strTmp = strTmp[:index] + currentValue + } + i = i + 1 + continue + } + if (strTmp != "") { + mustExpStk = append(mustExpStk, strTmp) + } + mustExpStk = append(mustExpStk, "(") + strTmp = "" + case ')': + if (strTmp != "") { + mustExpStk = append(mustExpStk, strTmp) + } + strTmp = "" + //Check Path - pop until ')' + mustExpStk, evalPath,_ := c.checkPathForTableEntry(tableName, currentValue, cfgData, + mustExpStk, ")") + if (evalPath != "") { + mustExpStk = append(mustExpStk, evalPath) + } + mustExpStk = append(mustExpStk, ")") + case '[': + if (strTmp != "") { + mustExpStk = append(mustExpStk, strTmp) + } + mustExpStk = append(mustExpStk, "[") + strTmp = "" + case ']': + if (strTmp != "") { + mustExpStk = append(mustExpStk, strTmp) + } + //Check Path - pop until = or '[' + mustExpStk, evalPath,_ := c.checkPathForTableEntry(tableName, currentValue, cfgData, + mustExpStk, "]") + if (evalPath != "") { + mustExpStk = append(mustExpStk, "[" + evalPath + "]") + } + strTmp = "" + default: + strTmp = fmt.Sprintf("%s%c", strTmp, mustExpStr[i]) + } + } + + //Get the redis data for accumulated keys and add them to session cache + depData := c.fetchDataToTmpCache() //fetch data to temp cache for temporary validation + + if (depData != nil) { + if (Tracing == true) { + TRACE_LOG(INFO_API, TRACE_CACHE, "Adding entries for 'must' expression : %s", c.yp.NodeDump(depData)) + } + } else { + //Could not fetch any entry from Redis after xpath evaluation + return CVL_FAILURE + } + + if errObj := c.yp.CacheSubtree(false, depData); errObj.ErrCode != yparser.YP_SUCCESS { + return CVL_FAILURE + } + + } //for each must expression + + return CVL_SUCCESS +} + +//Add all other table data for validating all 'must' exp for tableName +func (c *CVL) addTableDataForMustExp(op CVLOperation, tableName string) CVLRetCode { + if (modelInfo.tableInfo[tableName].mustExp == nil) { + return CVL_SUCCESS + } + + for mustTblName, mustOp := range modelInfo.tableInfo[tableName].tablesForMustExp { + //First check if must expression should be executed for the given operation + if (mustOp != OP_NONE) && ((mustOp & op) == OP_NONE) { + //must to be excuted for particular operation, but current operation + //is not the same one + continue + } + + //Check in global cache first and merge to session cache + if topNode, _ := dbCacheGet(mustTblName); topNode != nil { + var errObj yparser.YParserError + //If global cache has the table, add to the session validation + TRACE_LOG(INFO_API, TRACE_CACHE, "Adding global cache to session cache for table %s", tableName) + if errObj = c.yp.CacheSubtree(true, topNode); errObj.ErrCode != yparser.YP_SUCCESS { + return CVL_SYNTAX_ERROR + } + } else { //Put the must table in global table and add to session cache + cvg.cv.chkLeafRefWithOthCache = true + dbCacheSet(false, mustTblName, 100*DEFAULT_CACHE_DURATION) //Keep the cache for default duration + cvg.cv.chkLeafRefWithOthCache = false + + if topNode, ret := dbCacheGet(mustTblName); topNode != nil { + var errObj yparser.YParserError + //If global cache has the table, add to the session validation + TRACE_LOG(INFO_API, TRACE_CACHE, "Global cache created, add the data to session cache for table %s", tableName) + if errObj = c.yp.CacheSubtree(true, topNode); errObj.ErrCode != yparser.YP_SUCCESS { + return CVL_SYNTAX_ERROR + } + } else if (ret == CVL_SUCCESS) { + TRACE_LOG(INFO_API, TRACE_CACHE, "Global cache empty, no data in Redis for table %s", tableName) + return CVL_SUCCESS + } else { + CVL_LOG(ERROR ,"Could not create global cache for table %s", mustTblName) + return CVL_ERROR + } + + + /* + tableKeys, err:= redisClient.Keys(mustTblName + + modelInfo.tableInfo[mustTblName].redisKeyDelim + "*").Result() + + if (err != nil) { + continue + } + + for _, tableKey := range tableKeys { + tableKey = tableKey[len(mustTblName+ modelInfo.tableInfo[mustTblName].redisKeyDelim):] //remove table prefix + if (c.tmpDbCache[mustTblName] == nil) { + c.tmpDbCache[mustTblName] = map[string]interface{}{tableKey: nil} + } else { + tblMap := c.tmpDbCache[mustTblName] + tblMap.(map[string]interface{})[tableKey] =nil + c.tmpDbCache[mustTblName] = tblMap + } + } + */ + } + } + + return CVL_SUCCESS +} + +func (c *CVL) addTableEntryToCache(tableName string, redisKey string) { + if (tableName == "" || redisKey == "") { + return + } + + if (c.tmpDbCache[tableName] == nil) { + c.tmpDbCache[tableName] = map[string]interface{}{redisKey: nil} + } else { + tblMap := c.tmpDbCache[tableName] + tblMap.(map[string]interface{})[redisKey] =nil + c.tmpDbCache[tableName] = tblMap + } +} + +//Check delete constraint for leafref if key/field is deleted +func (c *CVL) checkDeleteConstraint(cfgData []CVLEditConfigData, + tableName, keyVal, field string) CVLRetCode { + var leafRefs []tblFieldPair + if (field != "") { + //Leaf or field is getting deleted + leafRefs = c.findUsedAsLeafRef(tableName, field) + } else { + //Entire entry is getting deleted + leafRefs = c.findUsedAsLeafRef(tableName, modelInfo.tableInfo[tableName].keys[0]) + } + + //The entry getting deleted might have been referred from multiple tables + //Return failure if at-least one table is using this entry + for _, leafRef := range leafRefs { + TRACE_LOG(INFO_API, (TRACE_DELETE | TRACE_SEMANTIC), "Checking delete constraint for leafRef %s/%s", leafRef.tableName, leafRef.field) + //Check in dependent data first, if the referred entry is already deleted + leafRefDeleted := false + for _, cfgDataItem := range cfgData { + if (cfgDataItem.VType == VALIDATE_NONE) && + (cfgDataItem.VOp == OP_DELETE ) && + (strings.HasPrefix(cfgDataItem.Key, (leafRef.tableName + modelInfo.tableInfo[leafRef.tableName].redisKeyDelim + keyVal + modelInfo.tableInfo[leafRef.tableName].redisKeyDelim))) { + //Currently, checking for one entry is being deleted in same session + //We should check for all entries + leafRefDeleted = true + break + } + } + + if (leafRefDeleted == true) { + continue //check next leafref + } + + //Else, check if any referred enrty is present in DB + var nokey []string + refKeyVal, err := luaScripts["find_key"].Run(redisClient, nokey, leafRef.tableName, + modelInfo.tableInfo[leafRef.tableName].redisKeyDelim, leafRef.field, keyVal).Result() + if (err == nil && refKeyVal != "") { + CVL_LOG(ERROR, "Delete will violate the constraint as entry %s is referred in %s", tableName, refKeyVal) + + return CVL_SEMANTIC_ERROR + } + } + + + return CVL_SUCCESS +} + +//Add the data which are referring this key +func (c *CVL) updateDeleteDataToCache(tableName string, redisKey string) { + if _, existing := c.tmpDbCache[tableName]; existing == false { + return + } else { + tblMap := c.tmpDbCache[tableName] + if _, existing := tblMap.(map[string]interface{})[redisKey]; existing == true { + delete(tblMap.(map[string]interface{}), redisKey) + c.tmpDbCache[tableName] = tblMap + } + } +} + +//Find which all tables (and which field) is using given (tableName/field) +// as leafref +//Use LUA script to find if table has any entry for this leafref + +type tblFieldPair struct { + tableName string + field string +} + +func (c *CVL) findUsedAsLeafRef(tableName, field string) []tblFieldPair { + + var tblFieldPairArr []tblFieldPair + + for tblName, tblInfo := range modelInfo.tableInfo { + if (tableName == tblName) { + continue + } + if (len(tblInfo.leafRef) == 0) { + continue + } + + for fieldName, leafRefs := range tblInfo.leafRef { + found := false + //Find leafref by searching table and field name + for _, leafRef := range leafRefs { + if ((strings.Contains(leafRef, tableName) == true) && + (strings.Contains(leafRef, field) == true)) { + tblFieldPairArr = append(tblFieldPairArr, + tblFieldPair{tblName, fieldName}) + //Found as leafref, no need to search further + found = true + break + } + } + + if (found == true) { + break + } + } + } + + return tblFieldPairArr +} + +//Add leafref entry for caching +//It has to be recursive in nature, as there can be chained leafref +func (c *CVL) addLeafRef(config bool, tableName string, name string, value string) { + + if (config == false) { + return + } + + //Check if leafRef entry is there for this field + if (len(modelInfo.tableInfo[tableName].leafRef[name]) > 0) { //array of leafrefs for a leaf + for _, leafRef := range modelInfo.tableInfo[tableName].leafRef[name] { + + //Get reference table name from the path and the leaf name + matches := reLeafRef.FindStringSubmatch(leafRef) + + //We have the leafref table name and the leaf name as well + if (matches != nil && len(matches) == 5) { //whole + 4 sub matches + refTableName := matches[2] + redisKey := value + + //Check if leafref dependency can also be met from 'must' table + if (c.chkLeafRefWithOthCache == true) { + found := false + for mustTbl, _ := range modelInfo.tableInfo[tableName].tablesForMustExp { + if mustTbl == refTableName { + found = true + break + } + } + if (found == true) { + //Leafref data will be available from must table dep data, skip this leafref entry + continue + } + } + + //only key is there, value wil be fetched and stored here, + //if value can't fetched this entry will be deleted that time + //Strip "_LIST" suffix + refRedisTableName := refTableName[0:len(refTableName) - len("_LIST")] + if (c.tmpDbCache[refRedisTableName] == nil) { + c.tmpDbCache[refRedisTableName] = map[string]interface{}{redisKey: nil} + } else { + tblMap := c.tmpDbCache[refRedisTableName] + _, exist := tblMap.(map[string]interface{})[redisKey] + if (exist == false) { + tblMap.(map[string]interface{})[redisKey] = nil + c.tmpDbCache[refRedisTableName] = tblMap + } + } + } + } + } +} + + +func (c *CVL) addChildLeaf(config bool, tableName string, parent *yparser.YParserNode, name string, value string) { + + /* If there is no value then assign default space string. */ + if len(value) == 0 { + value = " " + } + + //Batch leaf creation + c.batchLeaf = c.batchLeaf + name + "#" + value + "#" + //Check if this leaf has leafref, + //If so add the add redis key to its table so that those + // details can be fetched for dependency validation + + c.addLeafRef(config, tableName, name, value) +} + + +func (c *CVL) checkFieldMap(fieldMap *map[string]string) map[string]interface{} { + fieldMapNew := map[string]interface{}{} + + for field, value := range *fieldMap { + if (field == "NULL") { + continue + } else if (field[len(field)-1:] == "@") { + //last char @ means it is a leaf-list/array of fields + field = field[:len(field)-1] //strip @ + //split the values seprated using ',' + strArr := strings.Split(value, ",") + //fieldMapNew[field] = strings.Split(value, ",") + arrMap := make([]interface{}, 0)//len(strArr)) + for _, strArrItem := range strArr { + arrMap = append(arrMap, strArrItem) + } + fieldMapNew[field] = arrMap//make([]interface{}, len(strArr)) + } else { + fieldMapNew[field] = value + } + } + + return fieldMapNew +} + +//Merge 'src' map to 'dest' map of map[string]string type +func mergeMap(dest map[string]string, src map[string]string) { + for key, data := range src { + dest[key] = data + } +} + +// Fetch dependent data from validated data cache, +// Returns the data and flag to indicate that if requested data +// is found in update request, the data should be merged with Redis data +func (c *CVL) fetchDataFromRequestCache(tableName string, key string) (map[string]string, bool) { + cfgDataArr := c.requestCache[tableName][key] + if (cfgDataArr != nil) { + for _, cfgReqData := range cfgDataArr { + //Delete request doesn't have depedent data + if (cfgReqData.VOp == OP_CREATE) { + return cfgReqData.Data, false + } else if (cfgReqData.VOp == OP_UPDATE) { + return cfgReqData.Data, true + } + } + } + + return nil, false +} + +//Fetch given table entries using pipeline +func (c *CVL) fetchTableDataToTmpCache(tableName string, dbKeys map[string]interface{}) int { + + TRACE_LOG(INFO_API, TRACE_CACHE, "\n%v, Entered fetchTableDataToTmpCache", time.Now()) + + totalCount := len(dbKeys) + if (totalCount == 0) { + //No entry to be fetched + return 0 + } + + entryFetched := 0 + bulkCount := 0 + bulkKeys := []string{} + for dbKey, val := range dbKeys { //for all keys + + if (val != nil) { //skip entry already fetched + mapTable := c.tmpDbCache[tableName] + delete(mapTable.(map[string]interface{}), dbKey) //delete entry already fetched + totalCount = totalCount - 1 + if(bulkCount != totalCount) { + //If some entries are remaining go back to 'for' loop + continue + } + } else { + //Accumulate entries to be fetched + bulkKeys = append(bulkKeys, dbKey) + bulkCount = bulkCount + 1 + } + + if(bulkCount != totalCount) && ((bulkCount % MAX_BULK_ENTRIES_IN_PIPELINE) != 0) { + //If some entries are remaining and bulk bucket is not filled, + //go back to 'for' loop + continue + } + + mCmd := map[string]*redis.StringStringMapCmd{} + + pipe := redisClient.Pipeline() + + for _, dbKey := range bulkKeys { + + redisKey := tableName + modelInfo.tableInfo[tableName].redisKeyDelim + dbKey + //Check in validated cache first and add as dependent data + if entry, mergeNeeded := c.fetchDataFromRequestCache(tableName, dbKey); (entry != nil) { + c.tmpDbCache[tableName].(map[string]interface{})[dbKey] = entry + entryFetched = entryFetched + 1 + //Entry found in validated cache, so skip fetching from Redis + //if merging is not required with Redis DB + if (mergeNeeded == false) { + continue + } + } + + //Otherwise fetch it from Redis + mCmd[dbKey] = pipe.HGetAll(redisKey) //write into pipeline + if mCmd[dbKey] == nil { + CVL_LOG(ERROR, "Failed pipe.HGetAll('%s')", redisKey) + } + } + + _, err := pipe.Exec() + if err != nil { + CVL_LOG(ERROR, "Failed to fetch details for table %s", tableName) + return 0 + } + pipe.Close() + bulkKeys = nil + + mapTable := c.tmpDbCache[tableName] + + for key, val := range mCmd { + res, err := val.Result() + if (err != nil || len(res) == 0) { + //no data found, don't keep blank entry + delete(mapTable.(map[string]interface{}), key) + continue + } + //exclude table name and delim + keyOnly := key + + if (mapTable.(map[string]interface{})[keyOnly] != nil) { + tmpFieldMap := (mapTable.(map[string]interface{})[keyOnly]).(map[string]string) + //merge with validated cache data + mergeMap(res, tmpFieldMap) + fieldMap := c.checkFieldMap(&res) + mapTable.(map[string]interface{})[keyOnly] = fieldMap + } else { + fieldMap := c.checkFieldMap(&res) + mapTable.(map[string]interface{})[keyOnly] = fieldMap + } + + entryFetched = entryFetched + 1 + } + + runtime.Gosched() + } + + TRACE_LOG(INFO_API, TRACE_CACHE,"\n%v, Exiting fetchTableDataToTmpCache", time.Now()) + + return entryFetched +} + +//populate redis data to cache +func (c *CVL) fetchDataToTmpCache() *yparser.YParserNode { + TRACE_LOG(INFO_API, TRACE_CACHE, "\n%v, Entered fetchToTmpCache", time.Now()) + + entryToFetch := 0 + var root *yparser.YParserNode = nil + var errObj yparser.YParserError + + for entryToFetch = 1; entryToFetch > 0; { //Force to enter the loop for first time + //Repeat until all entries are fetched + entryToFetch = 0 + for tableName, dbKeys := range c.tmpDbCache { //for each table + entryToFetch = entryToFetch + c.fetchTableDataToTmpCache(tableName, dbKeys.(map[string]interface{})) + } //for each table + + //If no table entry delete the table itself + for tableName, dbKeys := range c.tmpDbCache { //for each table + if (len(dbKeys.(map[string]interface{})) == 0) { + delete(c.tmpDbCache, tableName) + continue + } + } + + if (entryToFetch == 0) { + //No more entry to fetch + break + } + + if (Tracing == true) { + jsonDataBytes, _ := json.Marshal(c.tmpDbCache) + jsonData := string(jsonDataBytes) + TRACE_LOG(INFO_API, TRACE_CACHE, "Top Node=%v\n", jsonData) + } + + data, err := jsonquery.ParseJsonMap(&c.tmpDbCache) + + if (err != nil) { + return nil + } + + //Build yang tree for each table and cache it + for jsonNode := data.FirstChild; jsonNode != nil; jsonNode=jsonNode.NextSibling { + TRACE_LOG(INFO_API, TRACE_CACHE, "Top Node=%v\n", jsonNode.Data) + //Visit each top level list in a loop for creating table data + topNode, _ := c.generateTableData(true, jsonNode) + if (root == nil) { + root = topNode + } else { + if root, errObj = c.yp.MergeSubtree(root, topNode); errObj.ErrCode != yparser.YP_SUCCESS { + return nil + } + } + } + } // until all dependent data is fetched + + if root != nil && Tracing == true { + dumpStr := c.yp.NodeDump(root) + TRACE_LOG(INFO_DETAIL, TRACE_CACHE, "Dependent Data = %v\n", dumpStr) + } + + TRACE_LOG(INFO_API, TRACE_CACHE, "\n%v, Exiting fetchToTmpCache", time.Now()) + return root +} + + +func (c *CVL) clearTmpDbCache() { + for key, _ := range c.tmpDbCache { + delete(c.tmpDbCache, key) + } +} + +func (c *CVL) generateTableFieldsData(config bool, tableName string, jsonNode *jsonquery.Node, +parent *yparser.YParserNode) CVLRetCode { + + //Traverse fields + for jsonFieldNode := jsonNode.FirstChild; jsonFieldNode!= nil; + jsonFieldNode = jsonFieldNode.NextSibling { + //Add fields as leaf to the list + if (jsonFieldNode.Type == jsonquery.ElementNode && + jsonFieldNode.FirstChild != nil && + jsonFieldNode.FirstChild.Type == jsonquery.TextNode) { + + if (len(modelInfo.tableInfo[tableName].mapLeaf) == 2) {//mapping should have two leaf always + //Values should be stored inside another list as map table + listNode := c.addChildNode(tableName, parent, tableName) //Add the list to the top node + c.addChildLeaf(config, tableName, + listNode, modelInfo.tableInfo[tableName].mapLeaf[0], + jsonFieldNode.Data) + + c.addChildLeaf(config, tableName, + listNode, modelInfo.tableInfo[tableName].mapLeaf[1], + jsonFieldNode.FirstChild.Data) + + } else { + //check if it is hash-ref, then need to add only key from "TABLE|k1" + hashRefMatch := reHashRef.FindStringSubmatch(jsonFieldNode.FirstChild.Data) + + if (hashRefMatch != nil && len(hashRefMatch) == 3) { + /*if (strings.HasPrefix(jsonFieldNode.FirstChild.Data, "[")) && + (strings.HasSuffix(jsonFieldNode.FirstChild.Data, "]")) && + (strings.Index(jsonFieldNode.FirstChild.Data, "|") > 0) {*/ + + c.addChildLeaf(config, tableName, + parent, jsonFieldNode.Data, + hashRefMatch[2]) //take hashref key value + } else { + c.addChildLeaf(config, tableName, + parent, jsonFieldNode.Data, + jsonFieldNode.FirstChild.Data) + } + } + + } else if (jsonFieldNode.Type == jsonquery.ElementNode && + jsonFieldNode.FirstChild != nil && + jsonFieldNode.FirstChild.Type == jsonquery.ElementNode) { + //Array data e.g. VLAN members + for arrayNode:=jsonFieldNode.FirstChild; arrayNode != nil; + + arrayNode = arrayNode.NextSibling { + c.addChildLeaf(config, tableName, + parent, jsonFieldNode.Data, + arrayNode.FirstChild.Data) + } + } + } + + return CVL_SUCCESS +} + +func (c *CVL) generateTableData(config bool, jsonNode *jsonquery.Node)(*yparser.YParserNode, CVLErrorInfo) { + var cvlErrObj CVLErrorInfo + + tableName := fmt.Sprintf("%s",jsonNode.Data) + c.batchLeaf = "" + + //Every Redis table is mapped as list within a container, + //E.g. ACL_RULE is mapped as + // container ACL_RULE { list ACL_RULE_LIST {} } + var topNode *yparser.YParserNode + + // Add top most conatiner e.g. 'container sonic-acl {...}' + if _, exists := modelInfo.tableInfo[tableName]; exists == false { + return nil, cvlErrObj + } + topNode = c.yp.AddChildNode(modelInfo.tableInfo[tableName].module, + nil, modelInfo.tableInfo[tableName].modelName) + + //Add the container node for each list + //e.g. 'container ACL_TABLE { list ACL_TABLE_LIST ...} + listConatinerNode := c.yp.AddChildNode(modelInfo.tableInfo[tableName].module, + topNode, tableName) + + //Traverse each key instance + for jsonNode = jsonNode.FirstChild; jsonNode != nil; jsonNode = jsonNode.NextSibling { + + //For each field check if is key + //If it is key, create list as child of top container + // Get all key name/value pairs + if yangListName := getRedisKeyToYangList(tableName, jsonNode.Data); yangListName!= "" { + tableName = yangListName + } + keyValuePair := getRedisToYangKeys(tableName, jsonNode.Data) + keyCompCount := len(keyValuePair) + totalKeyComb := 1 + var keyIndices []int + + //Find number of all key combinations + //Each key can have one or more key values, which results in nk1 * nk2 * nk2 combinations + idx := 0 + for i,_ := range keyValuePair { + totalKeyComb = totalKeyComb * len(keyValuePair[i].values) + keyIndices = append(keyIndices, 0) + } + + for ; totalKeyComb > 0 ; totalKeyComb-- { + //Get the YANG list name from Redis table name + //Ideally they are same except when one Redis table is split + //into multiple YANG lists + + //Add table i.e. create list element + listNode := c.addChildNode(tableName, listConatinerNode, tableName + "_LIST") //Add the list to the top node + + //For each key combination + //Add keys as leaf to the list + for idx = 0; idx < keyCompCount; idx++ { + c.addChildLeaf(config, tableName, + listNode, keyValuePair[idx].key, + keyValuePair[idx].values[keyIndices[idx]]) + } + + //Get all fields under the key field and add them as children of the list + c.generateTableFieldsData(config, tableName, jsonNode, listNode) + + //Check which key elements left after current key element + var next int = keyCompCount - 1 + for ((next > 0) && ((keyIndices[next] +1) >= len(keyValuePair[next].values))) { + next-- + } + //No more combination possible + if (next < 0) { + break + } + + keyIndices[next]++ + + //Reset indices for all other key elements + for idx = next+1; idx < keyCompCount; idx++ { + keyIndices[idx] = 0 + } + + TRACE_LOG(INFO_API, TRACE_CACHE, "Starting batch leaf creation - %s\n", c.batchLeaf) + //process batch leaf creation + if errObj := c.yp.AddMultiLeafNodes(modelInfo.tableInfo[tableName].module, listNode, c.batchLeaf); errObj.ErrCode != yparser.YP_SUCCESS { + cvlErrObj = CreateCVLErrObj(errObj) + return nil, cvlErrObj + } + c.batchLeaf = "" + } + } + + return topNode, cvlErrObj +} + +func (c *CVL) translateToYang(jsonMap *map[string]interface{}) (*yparser.YParserNode, CVLErrorInfo) { + + var cvlErrObj CVLErrorInfo + //Parse the map data to json tree + data, _ := jsonquery.ParseJsonMap(jsonMap) + var root *yparser.YParserNode + root = nil + var errObj yparser.YParserError + + for jsonNode := data.FirstChild; jsonNode != nil; jsonNode=jsonNode.NextSibling { + TRACE_LOG(INFO_API, TRACE_LIBYANG, "Top Node=%v\n", jsonNode.Data) + //Visit each top level list in a loop for creating table data + topNode, cvlErrObj := c.generateTableData(true, jsonNode) + + if topNode == nil { + cvlErrObj.ErrCode = CVL_SYNTAX_ERROR + return nil, cvlErrObj + } + + if (root == nil) { + root = topNode + } else { + if root, errObj = c.yp.MergeSubtree(root, topNode); errObj.ErrCode != yparser.YP_SUCCESS { + return nil, cvlErrObj + } + } + } + + return root, cvlErrObj +} + +//Validate config - syntax and semantics +func (c *CVL) validate (data *yparser.YParserNode) CVLRetCode { + + depData := c.fetchDataToTmpCache() + /* + if (depData != nil) { + if (0 != C.lyd_merge_to_ctx(&data, depData, C.LYD_OPT_DESTRUCT, ctx)) { + TRACE_LOG(1, "Failed to merge status data\n") + } + } + + if (0 != C.lyd_data_validate(&data, C.LYD_OPT_CONFIG, ctx)) { + fmt.Println("Validation failed\n") + return CVL_SYNTAX_ERROR + }*/ + + TRACE_LOG(INFO_DATA, TRACE_LIBYANG, "\nValidate1 data=%v\n", c.yp.NodeDump(data)) + errObj := c.yp.ValidateData(data, depData) + if yparser.YP_SUCCESS != errObj.ErrCode { + return CVL_FAILURE + } + + return CVL_SUCCESS +} + +func CreateCVLErrObj(errObj yparser.YParserError) CVLErrorInfo { + + cvlErrObj := CVLErrorInfo { + TableName : errObj.TableName, + ErrCode : CVLRetCode(errObj.ErrCode), + CVLErrDetails : cvlErrorMap[CVLRetCode(errObj.ErrCode)], + Keys : errObj.Keys, + Value : errObj.Value, + Field : errObj.Field, + Msg : errObj.Msg, + ConstraintErrMsg : errObj.ErrTxt, + ErrAppTag : errObj.ErrAppTag, + } + + + return cvlErrObj + +} + +//Perform syntax checks +func (c *CVL) validateSyntax(data *yparser.YParserNode) (CVLErrorInfo, CVLRetCode) { + var cvlErrObj CVLErrorInfo + TRACE_LOG(INFO_DATA, TRACE_LIBYANG, "Validating syntax \n....") + + if errObj := c.yp.ValidateSyntax(data); errObj.ErrCode != yparser.YP_SUCCESS { + + retCode := CVLRetCode(errObj.ErrCode) + + cvlErrObj = CVLErrorInfo { + TableName : errObj.TableName, + ErrCode : CVLRetCode(errObj.ErrCode), + CVLErrDetails : cvlErrorMap[retCode], + Keys : errObj.Keys, + Value : errObj.Value, + Field : errObj.Field, + Msg : errObj.Msg, + ConstraintErrMsg : errObj.ErrTxt, + ErrAppTag : errObj.ErrAppTag, + } + + + + return cvlErrObj, retCode + } + + return cvlErrObj, CVL_SUCCESS +} + +//Perform semantic checks +func (c *CVL) validateSemantics(data *yparser.YParserNode, appDepData *yparser.YParserNode) (CVLErrorInfo, CVLRetCode) { + var cvlErrObj CVLErrorInfo + + if (SkipSemanticValidation() == true) { + return cvlErrObj, CVL_SUCCESS + } + + //Get dependent data from + depData := c.fetchDataToTmpCache() //fetch data to temp cache for temporary validation + + if (Tracing == true) { + TRACE_LOG(INFO_API, TRACE_SEMANTIC, "Validating semantics data=%s\n depData =%s\n, appDepData=%s\n....", c.yp.NodeDump(data), c.yp.NodeDump(depData), c.yp.NodeDump(appDepData)) + } + + if errObj := c.yp.ValidateSemantics(data, depData, appDepData); errObj.ErrCode != yparser.YP_SUCCESS { + + retCode := CVLRetCode(errObj.ErrCode) + + cvlErrObj = CVLErrorInfo { + TableName : errObj.TableName, + ErrCode : CVLRetCode(errObj.ErrCode), + CVLErrDetails : cvlErrorMap[retCode], + Keys : errObj.Keys, + Value : errObj.Value, + Field : errObj.Field, + Msg : errObj.Msg, + ConstraintErrMsg : errObj.ErrTxt, + ErrAppTag : errObj.ErrAppTag, + } + + + + return cvlErrObj, retCode + } + + return cvlErrObj ,CVL_SUCCESS +} + +//Add config data item to accumulate per table +func (c *CVL) addCfgDataItem(configData *map[string]interface{}, + cfgDataItem CVLEditConfigData) (string, string){ + var cfgData map[string]interface{} + cfgData = *configData + + tblName, key := splitRedisKey(cfgDataItem.Key) + if (tblName == "" || key == "") { + //Bad redis key + return "", "" + } + + if (cfgDataItem.VOp == OP_DELETE) { + //Don't add data it is delete operation + return tblName, key + } + + if _, existing := cfgData[tblName]; existing { + fieldsMap := cfgData[tblName].(map[string]interface{}) + fieldsMap[key] = c.checkFieldMap(&cfgDataItem.Data) + } else { + fieldsMap := make(map[string]interface{}) + fieldsMap[key] = c.checkFieldMap(&cfgDataItem.Data) + cfgData[tblName] = fieldsMap + } + + return tblName, key +} + +//Get table entry from cache for redis key +func dbCacheEntryGet(tableName, key string) (*yparser.YParserNode, CVLRetCode) { + //First check if the table is cached + topNode, _ := dbCacheGet(tableName) + + + if (topNode != nil) { + //Convert to Yang keys + keyValuePair := getRedisToYangKeys(tableName, key) + + //Find if the entry is cached + keyCompStr := "" + for _, keyValItem := range keyValuePair { + keyCompStr = keyCompStr + fmt.Sprintf("[%s='%s']", + keyValItem.key, keyValItem.values[0]) + } + + entryNode := yparser.FindNode(topNode, fmt.Sprintf("//%s:%s/%s%s", + modelInfo.tableInfo[tableName].modelName, + modelInfo.tableInfo[tableName].modelName, + tableName, keyCompStr)) + + if (entryNode != nil) { + return entryNode, CVL_SUCCESS + } + } + + return nil, CVL_ERROR +} + +//Get the data from global cache +func dbCacheGet(tableName string) (*yparser.YParserNode, CVLRetCode) { + + TRACE_LOG(INFO_ALL, TRACE_CACHE, "Updating global cache for table %s", tableName) + dbCacheTmp, existing := cvg.db[tableName] + + if (existing == false) { + return nil, CVL_FAILURE //not even empty cache present + } + + if (dbCacheTmp.root != nil) { + if (dbCacheTmp.expiry != 0) { + //If cache is destroyable (i.e. expiry != 0), check if it has already expired. + //If not expired update the time stamp + if (time.Now().After(dbCacheTmp.startTime.Add(time.Second * time.Duration(dbCacheTmp.expiry)))) { + //Cache expired, clear the cache + dbCacheClear(tableName) + + return nil, CVL_ERROR + } + + //Since the cache is used actively, update the timestamp + dbCacheTmp.startTime = time.Now() + cvg.db[tableName] = dbCacheTmp + } + + return dbCacheTmp.root, CVL_SUCCESS + } else { + return nil, CVL_SUCCESS // return success for no entry in Redis db and hencec empty cache + } +} + +//Get the table data from redis and cache it in yang node format +//expiry =0 never expire the cache +func dbCacheSet(update bool, tableName string, expiry uint16) CVLRetCode { + + cvg.mutex.Lock() + + //Get the data from redis and save it + tableKeys, err:= redisClient.Keys(tableName + + modelInfo.tableInfo[tableName].redisKeyDelim + "*").Result() + + if (err != nil) { + cvg.mutex.Unlock() + return CVL_FAILURE + } + + TRACE_LOG(INFO_ALL, TRACE_CACHE, "Building global cache for table %s", tableName) + + tablePrefixLen := len(tableName + modelInfo.tableInfo[tableName].redisKeyDelim) + for _, tableKey := range tableKeys { + tableKey = tableKey[tablePrefixLen:] //remove table prefix + if (cvg.cv.tmpDbCache[tableName] == nil) { + cvg.cv.tmpDbCache[tableName] = map[string]interface{}{tableKey: nil} + } else { + tblMap := cvg.cv.tmpDbCache[tableName] + tblMap.(map[string]interface{})[tableKey] =nil + cvg.cv.tmpDbCache[tableName] = tblMap + } + } + + cvg.db[tableName] = dbCachedData{startTime:time.Now(), expiry: expiry, + root: cvg.cv.fetchDataToTmpCache()} + + if (Tracing == true) { + TRACE_LOG(INFO_ALL, TRACE_CACHE, "Cached Data = %v\n", cvg.cv.yp.NodeDump(cvg.db[tableName].root)) + } + + cvg.mutex.Unlock() + + //install keyspace notification for updating the cache + if (update == false) { + installDbChgNotif() + } + + + return CVL_SUCCESS +} + +//Receive all updates for all tables on a single channel +func installDbChgNotif() { + if (len(cvg.db) > 1) { //notif running for at least one table added previously + cvg.stopChan <- 1 //stop active notification + } + + subList := make([]string, 0) + for tableName, _ := range cvg.db { + subList = append(subList, + fmt.Sprintf("__keyspace@%d__:%s%s*", modelInfo.tableInfo[tableName].dbNum, + tableName, modelInfo.tableInfo[tableName].redisKeyDelim)) + + } + + //Listen on multiple channels + cvg.pubsub = redisClient.PSubscribe(subList...) + + go func() { + keySpacePrefixLen := len("__keyspace@4__:") + + notifCh := cvg.pubsub.Channel() + for { + select { + case <-cvg.stopChan: + //stop this routine + return + case msg:= <-notifCh: + //Handle update + tbl, key := splitRedisKey(msg.Channel[keySpacePrefixLen:]) + if (tbl != "" && key != "") { + dbCacheUpdate(tbl, key, msg.Payload) + } + } + } + }() +} + +func dbCacheUpdate(tableName, key, op string) CVLRetCode { + TRACE_LOG(INFO_ALL, TRACE_CACHE, "Updating global cache for table %s with key %s", tableName, key) + + //Find the node + //Delete the entry in yang tree + + cvg.mutex.Lock() + + node, _:= dbCacheEntryGet(tableName, key) + if (node != nil) { + //unlink and free the node + cvg.cv.yp.FreeNode(node) + } + + //Clear json map cache if any + cvg.cv.clearTmpDbCache() + + tableKeys := []string {key} + switch op { + case "hset", "hmset", "hdel": + //Get the entry from DB + for _, tableKey := range tableKeys { + cvg.cv.tmpDbCache[tableName] = map[string]interface{}{tableKey: nil} + } + + //Get the translated Yang tree + topNode := cvg.cv.fetchDataToTmpCache() + + //Merge the subtree with existing yang tree + var errObj yparser.YParserError + if (cvg.db[tableName].root != nil) { + if topNode, errObj = cvg.cv.yp.MergeSubtree(cvg.db[tableName].root, topNode); errObj.ErrCode != yparser.YP_SUCCESS { + cvg.mutex.Unlock() + return CVL_ERROR + } + } + + //Update DB map + db := cvg.db[tableName] + db.root = topNode + cvg.db[tableName] = db + + case "del": + //NOP, already deleted the entry + } + + cvg.mutex.Unlock() + + return CVL_SUCCESS +} + +//Clear cache data for given table +func dbCacheClear(tableName string) CVLRetCode { + cvg.cv.yp.FreeNode(cvg.db[tableName].root) + delete(cvg.db, tableName) + + TRACE_LOG(INFO_ALL, TRACE_CACHE, "Clearing global cache for table %s", tableName) + + return CVL_SUCCESS +} + diff --git a/cvl/cvl_api.go b/cvl/cvl_api.go new file mode 100644 index 000000000..9e42d190a --- /dev/null +++ b/cvl/cvl_api.go @@ -0,0 +1,514 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package cvl + +import ( + "fmt" + "encoding/json" + "github.com/go-redis/redis" + "path/filepath" + "github.com/Azure/sonic-mgmt-common/cvl/internal/yparser" + . "github.com/Azure/sonic-mgmt-common/cvl/internal/util" +) + +type CVLValidateType uint +const ( + VALIDATE_NONE CVLValidateType = iota //Data is used as dependent data + VALIDATE_SYNTAX //Syntax is checked and data is used as dependent data + VALIDATE_SEMANTICS //Semantics is checked + VALIDATE_ALL //Syntax and Semantics are checked +) + +type CVLOperation uint +const ( + OP_NONE CVLOperation = 0 //Used to just validate the config without any operation + OP_CREATE = 1 << 0//For Create operation + OP_UPDATE = 1 << 1//For Update operation + OP_DELETE = 1 << 2//For Delete operation +) + +var cvlErrorMap = map[CVLRetCode]string { + CVL_SUCCESS : "Config Validation Success", + CVL_SYNTAX_ERROR : "Config Validation Syntax Error", + CVL_SEMANTIC_ERROR : "Config Validation Semantic Error", + CVL_SYNTAX_MISSING_FIELD : "Required Field is Missing", + CVL_SYNTAX_INVALID_FIELD : "Invalid Field Received", + CVL_SYNTAX_INVALID_INPUT_DATA : "Invalid Input Data Received", + CVL_SYNTAX_MULTIPLE_INSTANCE : "Multiple Field Instances Received", + CVL_SYNTAX_DUPLICATE : "Duplicate Instances Received", + CVL_SYNTAX_ENUM_INVALID : "Invalid Enum Value Received", + CVL_SYNTAX_ENUM_INVALID_NAME : "Invalid Enum Value Received", + CVL_SYNTAX_ENUM_WHITESPACE : "Enum name with leading/trailing whitespaces Received", + CVL_SYNTAX_OUT_OF_RANGE : "Value out of range/length/pattern (data)", + CVL_SYNTAX_MINIMUM_INVALID : "min-elements constraint not honored", + CVL_SYNTAX_MAXIMUM_INVALID : "max-elements constraint not honored", + CVL_SEMANTIC_DEPENDENT_DATA_MISSING : "Dependent Data is missing", + CVL_SEMANTIC_MANDATORY_DATA_MISSING : "Mandatory Data is missing", + CVL_SEMANTIC_KEY_ALREADY_EXIST : "Key already existing.", + CVL_SEMANTIC_KEY_NOT_EXIST : "Key does not exist.", + CVL_SEMANTIC_KEY_DUPLICATE : "Duplicate key received", + CVL_SEMANTIC_KEY_INVALID : "Invalid Key Received", + CVL_INTERNAL_UNKNOWN : "Internal Unknown Error", + CVL_ERROR : "Generic Error", + CVL_NOT_IMPLEMENTED : "Error Not Implemented", + CVL_FAILURE : "Generic Failure", +} + +//Error code +type CVLRetCode int +const ( + CVL_SUCCESS CVLRetCode = iota + CVL_ERROR + CVL_NOT_IMPLEMENTED + CVL_INTERNAL_UNKNOWN + CVL_FAILURE + CVL_SYNTAX_ERROR = CVLRetCode(yparser.YP_SYNTAX_ERROR) + CVL_SEMANTIC_ERROR = CVLRetCode(yparser.YP_SEMANTIC_ERROR) + CVL_SYNTAX_MISSING_FIELD = CVLRetCode(yparser.YP_SYNTAX_MISSING_FIELD) + CVL_SYNTAX_INVALID_FIELD = CVLRetCode(yparser.YP_SYNTAX_INVALID_FIELD) /* Invalid Field */ + CVL_SYNTAX_INVALID_INPUT_DATA = CVLRetCode(yparser.YP_SYNTAX_INVALID_INPUT_DATA) /*Invalid Input Data */ + CVL_SYNTAX_MULTIPLE_INSTANCE = CVLRetCode(yparser.YP_SYNTAX_MULTIPLE_INSTANCE) /* Multiple Field Instances */ + CVL_SYNTAX_DUPLICATE = CVLRetCode(yparser.YP_SYNTAX_DUPLICATE) /* Duplicate Fields */ + CVL_SYNTAX_ENUM_INVALID = CVLRetCode(yparser.YP_SYNTAX_ENUM_INVALID) /* Invalid enum value */ + CVL_SYNTAX_ENUM_INVALID_NAME = CVLRetCode(yparser.YP_SYNTAX_ENUM_INVALID_NAME) /* Invalid enum name */ + CVL_SYNTAX_ENUM_WHITESPACE = CVLRetCode(yparser.YP_SYNTAX_ENUM_WHITESPACE) /* Enum name with leading/trailing whitespaces */ + CVL_SYNTAX_OUT_OF_RANGE = CVLRetCode(yparser.YP_SYNTAX_OUT_OF_RANGE) /* Value out of range/length/pattern (data) */ + CVL_SYNTAX_MINIMUM_INVALID = CVLRetCode(yparser.YP_SYNTAX_MINIMUM_INVALID) /* min-elements constraint not honored */ + CVL_SYNTAX_MAXIMUM_INVALID = CVLRetCode(yparser.YP_SYNTAX_MAXIMUM_INVALID) /* max-elements constraint not honored */ + CVL_SEMANTIC_DEPENDENT_DATA_MISSING = CVLRetCode(yparser.YP_SEMANTIC_DEPENDENT_DATA_MISSING) /* Dependent Data is missing */ + CVL_SEMANTIC_MANDATORY_DATA_MISSING = CVLRetCode(yparser.YP_SEMANTIC_MANDATORY_DATA_MISSING) /* Mandatory Data is missing */ + CVL_SEMANTIC_KEY_ALREADY_EXIST = CVLRetCode(yparser.YP_SEMANTIC_KEY_ALREADY_EXIST) /* Key already existing. */ + CVL_SEMANTIC_KEY_NOT_EXIST = CVLRetCode(yparser.YP_SEMANTIC_KEY_NOT_EXIST) /* Key is missing. */ + CVL_SEMANTIC_KEY_DUPLICATE = CVLRetCode(yparser.YP_SEMANTIC_KEY_DUPLICATE) /* Duplicate key. */ + CVL_SEMANTIC_KEY_INVALID = CVLRetCode(yparser.YP_SEMANTIC_KEY_INVALID) +) + +//Strcture for key and data in API +type CVLEditConfigData struct { + VType CVLValidateType //Validation type + VOp CVLOperation //Operation type + Key string //Key format : "PORT|Ethernet4" + Data map[string]string //Value : {"alias": "40GE0/28", "mtu" : 9100, "admin_status": down} +} + +func Initialize() CVLRetCode { + if (cvlInitialized == true) { + //CVL has already been initialized + return CVL_SUCCESS + } + + //Scan schema directory to get all schema files + modelFiles, err := filepath.Glob(CVL_SCHEMA + "/*.yin") + if err != nil { + CVL_LOG(FATAL ,"Could not read schema %v", err) + } + + yparser.Initialize() + + modelInfo.modelNs = make(map[string]modelNamespace) //redis table to model name + modelInfo.tableInfo = make(map[string]*modelTableInfo) //model namespace + modelInfo.allKeyDelims = make(map[string]bool) //all key delimiter + modelInfo.redisTableToYangList = make(map[string][]string) //Redis table to Yang list map + dbNameToDbNum = map[string]uint8{"APPL_DB": APPL_DB, "CONFIG_DB": CONFIG_DB} + + /* schema */ + for _, modelFilePath := range modelFiles { + _, modelFile := filepath.Split(modelFilePath) + + TRACE_LOG(INFO_DEBUG, TRACE_LIBYANG, "Parsing schema file %s ...\n", modelFilePath) + var module *yparser.YParserModule + if module, _ = yparser.ParseSchemaFile(modelFilePath); module == nil { + + CVL_LOG(FATAL,fmt.Sprintf("Unable to parse schema file %s", modelFile)) + return CVL_ERROR + } + + storeModelInfo(modelFile, module) + } + + //Add all table names to be fetched to validate 'must' expression + addTableNamesForMustExp() + + //Initialize redis Client + + redisClient = redis.NewClient(&redis.Options{ + Addr: ":6379", + Password: "", // no password set + DB: int(CONFIG_DB), // use APP DB + }) + + if (redisClient == nil) { + CVL_LOG(FATAL, "Unable to connect with Redis Config DB") + return CVL_ERROR + } + + //Load lua script into redis + loadLuaScript() + + cvlInitialized = true + + return CVL_SUCCESS +} + +func Finish() { + yparser.Finish() +} + +func ValidationSessOpen() (*CVL, CVLRetCode) { + cvl := &CVL{} + cvl.tmpDbCache = make(map[string]interface{}) + cvl.requestCache = make(map[string]map[string][]CVLEditConfigData) + cvl.yp = &yparser.YParser{} + + if (cvl == nil || cvl.yp == nil) { + return nil, CVL_FAILURE + } + + return cvl, CVL_SUCCESS +} + +func ValidationSessClose(c *CVL) CVLRetCode { + c.yp.DestroyCache() + c = nil + + return CVL_SUCCESS +} + +func (c *CVL) ValidateStartupConfig(jsonData string) CVLRetCode { + //Check config data syntax + //Finally validate + return CVL_NOT_IMPLEMENTED +} + +func (c *CVL) ValidateIncrementalConfig(jsonData string) CVLRetCode { + //Check config data syntax + //Fetch the depedent data + //Merge config and dependent data + //Finally validate + c.clearTmpDbCache() + var v interface{} + + b := []byte(jsonData) + if err := json.Unmarshal(b, &v); err != nil { + return CVL_SYNTAX_ERROR + } + + var dataMap map[string]interface{} = v.(map[string]interface{}) + + root, _ := c.translateToYang(&dataMap) + if root == nil { + return CVL_SYNTAX_ERROR + + } + + //Add and fetch entries if already exists in Redis + for tableName, data := range dataMap { + for key, _ := range data.(map[string]interface{}) { + c.addTableEntryToCache(tableName, key) + } + } + + existingData := c.fetchDataToTmpCache() + + //Merge existing data for update syntax or checking duplicate entries + var errObj yparser.YParserError + if (existingData != nil) { + if root, errObj = c.yp.MergeSubtree(root, existingData); + errObj.ErrCode != yparser.YP_SUCCESS { + return CVL_ERROR + } + } + + //Clear cache + c.clearTmpDbCache() + + //Add tables for 'must' expression + for tableName, _ := range dataMap { + c.addTableDataForMustExp(OP_NONE, tableName) + } + + //Perform validation + if _, cvlRetCode := c.validateSemantics(root, nil); cvlRetCode != CVL_SUCCESS { + return cvlRetCode + } + + return CVL_SUCCESS +} + +//Validate data for operation +func (c *CVL) ValidateConfig(jsonData string) CVLRetCode { + c.clearTmpDbCache() + var v interface{} + + b := []byte(jsonData) + if err := json.Unmarshal(b, &v); err == nil { + var value map[string]interface{} = v.(map[string]interface{}) + root, _ := c.translateToYang(&value) + //if ret == CVL_SUCCESS && root != nil { + if root == nil { + return CVL_FAILURE + + } + + if (c.validate(root) != CVL_SUCCESS) { + return CVL_FAILURE + } + + } + + return CVL_SUCCESS +} + +//Validate config data based on edit operation - no marshalling in between +func (c *CVL) ValidateEditConfig(cfgData []CVLEditConfigData) (CVLErrorInfo, CVLRetCode) { + var cvlErrObj CVLErrorInfo + + if (SkipValidation() == true) { + + return cvlErrObj, CVL_SUCCESS + } + + c.clearTmpDbCache() + + //Step 1: Get requested dat first + //add all dependent data to be fetched from Redis + requestedData := make(map[string]interface{}) + + for _, cfgDataItem := range cfgData { + if (VALIDATE_ALL != cfgDataItem.VType) { + continue + } + + //Add config data item to be validated + tbl,key := c.addCfgDataItem(&requestedData, cfgDataItem) + + //Add to request cache + reqTbl, exists := c.requestCache[tbl] + if (exists == false) { + //Create new table key data + reqTbl = make(map[string][]CVLEditConfigData) + } + cfgDataItemArr, _ := reqTbl[key] + cfgDataItemArr = append(cfgDataItemArr, cfgDataItem) + reqTbl[key] = cfgDataItemArr + c.requestCache[tbl] = reqTbl + + //Invalid table name or invalid key separator + if key == "" { + cvlErrObj.ErrCode = CVL_SYNTAX_ERROR + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SYNTAX_ERROR + } + + switch cfgDataItem.VOp { + case OP_CREATE: + if (c.addTableEntryForMustExp(&cfgDataItem, tbl) != CVL_SUCCESS) { + c.addTableDataForMustExp(cfgDataItem.VOp, tbl) + } + + case OP_UPDATE: + //Get the existing data from Redis to cache, so that final validation can be done after merging this dependent data + if (c.addTableEntryForMustExp(&cfgDataItem, tbl) != CVL_SUCCESS) { + c.addTableDataForMustExp(cfgDataItem.VOp, tbl) + } + c.addTableEntryToCache(tbl, key) + + case OP_DELETE: + if (len(cfgDataItem.Data) > 0) { + //Delete a single field + if (len(cfgDataItem.Data) != 1) { + CVL_LOG(ERROR, "Only single field is allowed for field deletion") + } else { + for field, _ := range cfgDataItem.Data { + if (c.checkDeleteConstraint(cfgData, tbl, key, field) != CVL_SUCCESS) { + cvlErrObj.ErrCode = CVL_SEMANTIC_ERROR + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SEMANTIC_ERROR + } + break //only one field there + } + } + } else { + //Entire entry to be deleted + if (c.checkDeleteConstraint(cfgData, tbl, key, "") != CVL_SUCCESS) { + cvlErrObj.ErrCode = CVL_SEMANTIC_ERROR + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SEMANTIC_ERROR + } + //TBD : Can we do this ? + //No entry has depedency on this key, + //remove from requestCache, we don't need any more as depedent data + //delete(c.requestCache[tbl], key) + } + + if (c.addTableEntryForMustExp(&cfgDataItem, tbl) != CVL_SUCCESS) { + c.addTableDataForMustExp(cfgDataItem.VOp, tbl) + } + + c.addTableEntryToCache(tbl, key) + } + } + + //Only for tracing + if (IsTraceSet()) { + jsonData := "" + + jsonDataBytes, err := json.Marshal(requestedData) + if (err == nil) { + jsonData = string(jsonDataBytes) + } else { + cvlErrObj.ErrCode = CVL_SYNTAX_ERROR + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SYNTAX_ERROR + } + + TRACE_LOG(INFO_DATA, TRACE_LIBYANG, "Requested JSON Data = [%s]\n", jsonData) + } + + //Step 2 : Perform syntax validation only + yang, errN := c.translateToYang(&requestedData) + if (errN.ErrCode == CVL_SUCCESS) { + if cvlErrObj, cvlRetCode := c.validateSyntax(yang); cvlRetCode != CVL_SUCCESS { + return cvlErrObj, cvlRetCode + } + } else { + return errN,errN.ErrCode + } + + //Step 3 : Check keys and update dependent data + dependentData := make(map[string]interface{}) + + for _, cfgDataItem := range cfgData { + + if (cfgDataItem.VType == VALIDATE_ALL || cfgDataItem.VType == VALIDATE_SEMANTICS) { + //Step 3.1 : Check keys + switch cfgDataItem.VOp { + case OP_CREATE: + //Check key should not already exist + n, err1 := redisClient.Exists(cfgDataItem.Key).Result() + if (err1 == nil && n > 0) { + //Check if key deleted and CREATE done in same session, + //allow to create the entry + tbl, key := splitRedisKey(cfgDataItem.Key) + deletedInSameSession := false + if tbl != "" && key != "" { + for _, cachedCfgData := range c.requestCache[tbl][key] { + if cachedCfgData.VOp == OP_DELETE { + deletedInSameSession = true + break + } + } + } + + if deletedInSameSession == false { + CVL_LOG(ERROR, "\nValidateEditConfig(): Key = %s already exists", cfgDataItem.Key) + cvlErrObj.ErrCode = CVL_SEMANTIC_KEY_ALREADY_EXIST + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SEMANTIC_KEY_ALREADY_EXIST + + } else { + TRACE_LOG(INFO_API, TRACE_CREATE, "\nKey %s is deleted in same session, skipping key existence check for OP_CREATE operation", cfgDataItem.Key) + } + } + + c.yp.SetOperation("CREATE") + + case OP_UPDATE: + n, err1 := redisClient.Exists(cfgDataItem.Key).Result() + if (err1 != nil || n == 0) { //key must exists + CVL_LOG(ERROR, "\nValidateEditConfig(): Key = %s does not exist", cfgDataItem.Key) + cvlErrObj.ErrCode = CVL_SEMANTIC_KEY_ALREADY_EXIST + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SEMANTIC_KEY_NOT_EXIST + } + + c.yp.SetOperation("UPDATE") + + case OP_DELETE: + n, err1 := redisClient.Exists(cfgDataItem.Key).Result() + if (err1 != nil || n == 0) { //key must exists + CVL_LOG(ERROR, "\nValidateEditConfig(): Key = %s does not exist", cfgDataItem.Key) + cvlErrObj.ErrCode = CVL_SEMANTIC_KEY_NOT_EXIST + cvlErrObj.CVLErrDetails = cvlErrorMap[cvlErrObj.ErrCode] + return cvlErrObj, CVL_SEMANTIC_KEY_NOT_EXIST + } + + c.yp.SetOperation("DELETE") + //store deleted keys + } + + }/* else if (cfgDataItem.VType == VALIDATE_NONE) { + //Step 3.2 : Get dependent data + + switch cfgDataItem.VOp { + case OP_CREATE: + //NOP + case OP_UPDATE: + //NOP + case OP_DELETE: + tbl,key := c.addCfgDataItem(&dependentData, cfgDataItem) + //update cache by removing deleted entry + c.updateDeleteDataToCache(tbl, key) + //store deleted keys + } + }*/ + } + + var depYang *yparser.YParserNode = nil + if (len(dependentData) > 0) { + depYang, errN = c.translateToYang(&dependentData) + } + //Step 4 : Perform validation + if cvlErrObj, cvlRetCode1 := c.validateSemantics(yang, depYang); cvlRetCode1 != CVL_SUCCESS { + return cvlErrObj, cvlRetCode1 + } + + //Cache validated data + /* + if errObj := c.yp.CacheSubtree(false, yang); errObj.ErrCode != yparser.YP_SUCCESS { + TRACE_LOG(INFO_API, TRACE_CACHE, "Could not cache validated data") + } + */ + + c.yp.DestroyCache() + return cvlErrObj, CVL_SUCCESS +} + +/* Fetch the Error Message from CVL Return Code. */ +func GetErrorString(retCode CVLRetCode) string{ + + return cvlErrorMap[retCode] + +} + +//Validate key only +func (c *CVL) ValidateKeys(key []string) CVLRetCode { + return CVL_NOT_IMPLEMENTED +} + +//Validate key and data +func (c *CVL) ValidateKeyData(key string, data string) CVLRetCode { + return CVL_NOT_IMPLEMENTED +} + +//Validate key, field and value +func (c *CVL) ValidateFields(key string, field string, value string) CVLRetCode { + return CVL_NOT_IMPLEMENTED +} diff --git a/cvl/cvl_luascript.go b/cvl/cvl_luascript.go new file mode 100644 index 000000000..fd4c863ad --- /dev/null +++ b/cvl/cvl_luascript.go @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package cvl +import ( + "github.com/go-redis/redis" +) + +//Redis server side script +func loadLuaScript() { + luaScripts = make(map[string]*redis.Script) + + // Find entry which has given fieldName and value + luaScripts["find_key"] = redis.NewScript(` + local tableName=ARGV[1] + local sep=ARGV[2] + local fieldName=ARGV[3] + local fieldValue=ARGV[4] + + -- Check if field value is part of key + local entries=redis.call('KEYS', tableName..sep.."*"..fieldValue.."*") + + if (entries[1] ~= nil) + then + return entries[1] + else + + -- Search through all keys for fieldName and fieldValue + local entries=redis.call('KEYS', tableName..sep.."*") + + local idx = 1 + while(entries[idx] ~= nil) + do + local val = redis.call("HGET", entries[idx], fieldName) + if (val == fieldValue) + then + -- Return the key + return entries[idx] + end + + idx = idx + 1 + end + end + + -- Could not find the key + return "" + `) + +} diff --git a/cvl/cvl_test.go b/cvl/cvl_test.go new file mode 100644 index 000000000..afd3e80ef --- /dev/null +++ b/cvl/cvl_test.go @@ -0,0 +1,3491 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package cvl_test + +import ( + "github.com/Azure/sonic-mgmt-common/cvl" + "encoding/json" + "fmt" + "github.com/go-redis/redis" + "io/ioutil" + "os" + "os/exec" + "strings" + "syscall" + "testing" + "runtime" + . "github.com/Azure/sonic-mgmt-common/cvl/internal/util" + "github.com/Azure/sonic-mgmt-common/cvl/internal/yparser" +) + +type testEditCfgData struct { + filedescription string + cfgData string + depData string + retCode cvl.CVLRetCode +} + +var rclient *redis.Client +var port_map map[string]interface{} +var filehandle *os.File + +/* Dependent port channel configuration. */ +var depDataMap = map[string]interface{} { + "PORTCHANNEL" : map[string]interface{} { + "PortChannel001": map[string] interface{} { + "admin_status": "up", + "mtu": "9100", + }, + "PortChannel002": map[string] interface{} { + "admin_status": "up", + "mtu": "9100", + }, + }, + "PORTCHANNEL_MEMBER": map[string]interface{} { + "PortChannel001|Ethernet4": map[string] interface{} { + "NULL": "NULL", + }, + "PortChannel001|Ethernet8": map[string] interface{} { + "NULL": "NULL", + }, + "PortChannel001|Ethernet12": map[string] interface{} { + "NULL": "NULL", + }, + "PortChannel002|Ethernet20": map[string] interface{} { + "NULL": "NULL", + }, + "PortChannel002|Ethernet24": map[string] interface{} { + "NULL": "NULL", + }, + }, +} + +/* Converts JSON Data in a File to Map. */ +func convertJsonFileToMap(t *testing.T, fileName string) map[string]string { + var mapstr map[string]string + + jsonData := convertJsonFileToString(t, fileName) + byteData := []byte(jsonData) + + err := json.Unmarshal(byteData, &mapstr) + + if err != nil { + fmt.Println("Failed to convert Json File to map:", err) + } + + return mapstr + +} + +/* Converts JSON Data in a File to Map. */ +func convertDataStringToMap(t *testing.T, dataString string) map[string]string { + var mapstr map[string]string + + byteData := []byte(dataString) + + err := json.Unmarshal(byteData, &mapstr) + + if err != nil { + fmt.Println("Failed to convert Json Data String to map:", err) + } + + return mapstr + +} + +/* Converts JSON Data in a File to String. */ +func convertJsonFileToString(t *testing.T, fileName string) string { + var jsonData string + + data, err := ioutil.ReadFile(fileName) + + if err != nil { + fmt.Printf("\nFailed to read data file : %v\n", err) + } else { + jsonData = string(data) + } + + return jsonData +} + +/* Converts JSON config to map which can be loaded to Redis */ +func loadConfig(key string, in []byte) map[string]interface{} { + var fvp map[string]interface{} + + err := json.Unmarshal(in, &fvp) + if err != nil { + fmt.Printf("Failed to Unmarshal %v err: %v", in, err) + } + if key != "" { + kv := map[string]interface{}{} + kv[key] = fvp + return kv + } + return fvp +} + +/* Separator for keys. */ +func getSeparator() string { + return "|" +} + +/* Unloads the Config DB based on JSON File. */ +func unloadConfigDB(rclient *redis.Client, mpi map[string]interface{}) { + for key, fv := range mpi { + switch fv.(type) { + case map[string]interface{}: + for subKey, subValue := range fv.(map[string]interface{}) { + newKey := key + getSeparator() + subKey + _, err := rclient.Del(newKey).Result() + + if err != nil { + fmt.Printf("Invalid data for db: %v : %v %v", newKey, subValue, err) + } + + } + default: + fmt.Printf("Invalid data for db: %v : %v", key, fv) + } + } + +} + +/* Loads the Config DB based on JSON File. */ +func loadConfigDB(rclient *redis.Client, mpi map[string]interface{}) { + for key, fv := range mpi { + switch fv.(type) { + case map[string]interface{}: + for subKey, subValue := range fv.(map[string]interface{}) { + newKey := key + getSeparator() + subKey + _, err := rclient.HMSet(newKey, subValue.(map[string]interface{})).Result() + + if err != nil { + fmt.Printf("Invalid data for db: %v : %v %v", newKey, subValue, err) + } + + } + default: + fmt.Printf("Invalid data for db: %v : %v", key, fv) + } + } +} + +func compareErrorDetails(cvlErr cvl.CVLErrorInfo, expCode cvl.CVLRetCode, errAppTag string, constraintmsg string) bool { + + if ((cvlErr.ErrCode == expCode) && ((cvlErr.ErrAppTag == errAppTag) || (cvlErr.ConstraintErrMsg == constraintmsg))) { + return true + } + + return false +} + +func getConfigDbClient() *redis.Client { + rclient := redis.NewClient(&redis.Options{ + Network: "tcp", + Addr: "localhost:6379", + Password: "", // no password set + DB: 4, + DialTimeout: 0, + }) + _, err := rclient.Ping().Result() + if err != nil { + fmt.Printf("failed to connect to redis server %v", err) + } + return rclient +} + +/* Prepares the database in Redis Server. */ +func prepareDb() { + rclient = getConfigDbClient() + + fileName := "testdata/port_table.json" + PortsMapByte, err := ioutil.ReadFile(fileName) + if err != nil { + fmt.Printf("read file %v err: %v", fileName, err) + } + + port_map = loadConfig("", PortsMapByte) + + loadConfigDB(rclient, port_map) + loadConfigDB(rclient, depDataMap) +} + +func WriteToFile(message string) { + pc := make([]uintptr, 10) + runtime.Callers(2, pc) + f := runtime.FuncForPC(pc[0]) + + message = f.Name()+ "\n" + message + + if _, err := filehandle.Write([]byte(message)); err != nil { + fmt.Println("Unable to write to cvl test log file") + } + + message = "\n-------------------------------------------------\n" + + + if _, err := filehandle.Write([]byte(message)); err != nil { + fmt.Println("Unable to write to cvl test log file") + } +} + +/* Setup before starting of test. */ +func TestMain(m *testing.M) { + + redisAlreadyRunning := false + pidOfRedis, err := exec.Command("/bin/pidof", "redis-server").Output() + if err == nil && string(pidOfRedis) != "\n" { + redisAlreadyRunning = true + } + + if (redisAlreadyRunning == false) { + //Redis not running, lets start it + _, err := exec.Command("/bin/sh", "-c", "sudo /etc/init.d/redis-server start").Output() + if err != nil { + fmt.Println(err.Error()) + } + + } + + os.Remove("testdata/cvl_test_details.log") + + filehandle, err = os.OpenFile("testdata/cvl_test_details.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + + if err != nil { + fmt.Println("Could not open the log file for writing.") + } + + + /* Prepare the Redis database. */ + prepareDb() + SetTrace(true) + cvl.Debug(true) + code := m.Run() + //os.Exit(m.Run()) + + unloadConfigDB(rclient, port_map) + unloadConfigDB(rclient, depDataMap) + cvl.Finish() + rclient.Close() + rclient.FlushDB() + + if err := filehandle.Close(); err != nil { + //log.Fatal(err) + } + + if (redisAlreadyRunning == false) { + //If Redis was not already running, close the instance that we ran + _, err := exec.Command("/bin/sh", "-c", "sudo /etc/init.d/redis-server stop").Output() + if err != nil { + fmt.Println(err.Error()) + } + + } + + os.Exit(code) + +} + +//Test Initialize() API +func TestInitialize(t *testing.T) { + ret := cvl.Initialize() + if (ret != cvl.CVL_SUCCESS) { + t.Errorf("CVl initialization failed") + } + + ret = cvl.Initialize() + if (ret != cvl.CVL_SUCCESS) { + t.Errorf("CVl re-initialization should not fail") + } +} + +//Test Initialize() API +func TestFinish(t *testing.T) { + ret := cvl.Initialize() + if (ret != cvl.CVL_SUCCESS) { + t.Errorf("CVl initialization failed") + } + + cvl.Finish() + + //Initialize again for other test cases to run + cvl.Initialize() +} + +/* ValidateEditConfig with user input in file . */ +func TestValidateEditConfig_CfgFile(t *testing.T) { + + tests := []struct { + filedescription string + cfgDataFile string + depDataFile string + retCode cvl.CVLRetCode + }{ + {filedescription: "ACL_DATA", cfgDataFile: "testdata/aclrule.json", depDataFile: "testdata/acltable.json", retCode: cvl.CVL_SUCCESS}, + } + + cvSess, _ := cvl.ValidationSessOpen() + + for index, tc := range tests { + t.Logf("Running Testcase %d with Description %s", index+1, tc.filedescription) + + t.Run(tc.filedescription, func(t *testing.T) { + + jsonEditCfg_Create_DependentMap := convertJsonFileToMap(t, tc.depDataFile) + jsonEditCfg_Create_ConfigMap := convertJsonFileToMap(t, tc.cfgDataFile) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{cvl.VALIDATE_ALL, cvl.OP_CREATE, "ACL_TABLE|TestACL1", jsonEditCfg_Create_DependentMap}, + } + + + cvlErrObj, err := cvSess.ValidateEditConfig(cfgData) + + if err != tc.retCode { + t.Errorf("Config Validation failed. %v", cvlErrObj) + } + + cfgData = []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{cvl.VALIDATE_ALL, cvl.OP_CREATE, "ACL_RULE|TestACL1|Rule1", jsonEditCfg_Create_ConfigMap}, + } + + + cvlErrObj, err = cvSess.ValidateEditConfig(cfgData) + + if err != tc.retCode { + t.Errorf("Config Validation failed. %v", cvlErrObj) + } + }) + } + + cvl.ValidationSessClose(cvSess) +} + +/* ValidateEditConfig with user input inline. */ +func TestValidateEditConfig_CfgStrBuffer(t *testing.T) { + + type testStruct struct { + filedescription string + cfgData string + depData string + retCode cvl.CVLRetCode + } + + cvSess, _ := cvl.ValidationSessOpen() + + tests := []testStruct{} + + /* Iterate through data present is separate file. */ + for index, _ := range json_edit_config_create_acl_table_dependent_data { + tests = append(tests, testStruct{filedescription: "ACL_DATA", cfgData: json_edit_config_create_acl_rule_config_data[index], + depData: json_edit_config_create_acl_table_dependent_data[index], retCode: cvl.CVL_SUCCESS}) + } + + for index, tc := range tests { + t.Logf("Running Testcase %d with Description %s", index+1, tc.filedescription) + t.Run(tc.filedescription, func(t *testing.T) { + jsonEditCfg_Create_DependentMap := convertDataStringToMap(t, tc.depData) + jsonEditCfg_Create_ConfigMap := convertDataStringToMap(t, tc.cfgData) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{cvl.VALIDATE_ALL, cvl.OP_CREATE, "ACL_TABLE|TestACL1", jsonEditCfg_Create_DependentMap}, + } + + + cvlErrObj, err := cvSess.ValidateEditConfig(cfgData) + + if err != tc.retCode { + t.Errorf("Config Validation failed. %v", cvlErrObj) + } + + cfgData = []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{cvl.VALIDATE_ALL, cvl.OP_CREATE, "ACL_RULE|TestACL1|Rule1", jsonEditCfg_Create_ConfigMap}, + } + + + cvlErrObj, err = cvSess.ValidateEditConfig(cfgData) + + if err != tc.retCode { + t.Errorf("Config Validation failed. %v", cvlErrObj) + } + }) + } + + cvl.ValidationSessClose(cvSess) +} +/* API when config is given as string buffer. */ +func TestValidateConfig_CfgStrBuffer(t *testing.T) { + type testStruct struct { + filedescription string + jsonString string + retCode cvl.CVLRetCode + } + + tests := []testStruct{} + + for index, _ := range json_validate_config_data { + // Fetch the modelName. + result := strings.Split(json_validate_config_data[index], "{") + modelName := strings.Trim(strings.Replace(strings.TrimSpace(result[1]), "\"", "", -1), ":") + + tests = append(tests, testStruct{filedescription: modelName, jsonString: json_validate_config_data[index], retCode: cvl.CVL_SUCCESS}) + } + + cvSess, _ := cvl.ValidationSessOpen() + + for index, tc := range tests { + t.Logf("Running Testcase %d with Description %s", index+1, tc.filedescription) + t.Run(fmt.Sprintf("%s [%d]", tc.filedescription, index+1), func(t *testing.T) { + err := cvSess.ValidateConfig(tc.jsonString) + + + if err != tc.retCode { + t.Errorf("Config Validation failed.") + } + + }) + } + + cvl.ValidationSessClose(cvSess) + +} + + +/* API when config is given as json file. */ +func TestValidateConfig_CfgFile(t *testing.T) { + + /* Structure containing file information. */ + tests := []struct { + filedescription string + fileName string + retCode cvl.CVLRetCode + }{ + {filedescription: "Config File - VLAN,ACL,PORTCHANNEL", fileName: "testdata/config_db1.json", retCode: cvl.CVL_SUCCESS}, + } + + cvSess, _ := cvl.ValidationSessOpen() + + for index, tc := range tests { + + t.Logf("Running Testcase %d with Description %s", index+1, tc.filedescription) + t.Run(tc.filedescription, func(t *testing.T) { + jsonString := convertJsonFileToString(t, tc.fileName) + err := cvSess.ValidateConfig(jsonString) + + + if err != tc.retCode { + t.Errorf("Config Validation failed.") + } + + }) + } + + cvl.ValidationSessClose(cvSess) +} + +func TestValidateEditConfig_Delete_Must_Check_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "PORT" : map[string]interface{} { + "Ethernet3" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + "index": "3", + }, + "Ethernet5" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "85,86,87,89", + "mtu": "9100", + "index": "5", + }, + }, + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + "ports@": "Ethernet3,Ethernet5", + }, + "TestACL2": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + "ACL_RULE" : map[string]interface{} { + "TestACL1|Rule1": map[string] interface{} { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + "TestACL2|Rule2": map[string] interface{} { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cfgDataAclRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|TestACL2|Rule2", + map[string]string { + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrObj, err := cvSess.ValidateEditConfig(cfgDataAclRule) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { //should not succeed + t.Errorf("Config Validation failed. %v", cvlErrObj) + } + + unloadConfigDB(rclient, depDataMap) +} + +/* +func TestValidateEditConfig_Delete_Must_Check_Negative(t *testing.T) { + depDataMap := map[string]interface{} { + "PORT" : map[string]interface{} { + "Ethernet3" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + }, + "Ethernet5" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "85,86,87,89", + "mtu": "9100", + }, + }, + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + "ports@": "Ethernet3,Ethernet5", + }, + }, + "ACL_RULE" : map[string]interface{} { + "TestACL1|Rule1": map[string] interface{} { + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cfgDataAclRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|TestACL1|Rule1", + map[string]string { + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrObj, err := cvSess.ValidateEditConfig(cfgDataAclRule) + + cvl.ValidationSessClose(cvSess) + + if err == cvl.CVL_SUCCESS { //should not succeed + t.Errorf("Config Validation failed. %v", cvlErrObj) + } + + unloadConfigDB(rclient, depDataMap) +} +*/ + +//Validate invalid json data +func TestValidateConfig_Negative(t *testing.T) { + cvSess, _ := cvl.ValidationSessOpen() + jsonData := `{ + "VLANjunk": { + "Vlan100": { + "members": [ + "Ethernet4", + "Ethernet8" + ], + "vlanid": "100" + } + } + }` + + err := cvSess.ValidateConfig(jsonData) + + if err == cvl.CVL_SUCCESS { //Should return failure + t.Errorf("Config Validation failed.") + } + + cvl.ValidationSessClose(cvSess) +} + +/* Delete Existing Key.*/ +/* +func TestValidateEditConfig_Delete_Semantic_ACLTableReference_Positive(t *testing.T) { + + depDataMap := map[string]interface{} { + "ACL_TABLE" : map[string]interface{} { + "TestACL1005": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + "ACL_RULE": map[string]interface{} { + "TestACL1005|Rule1": map[string] interface{} { + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|TestACL1005|Rule1", + map[string]string{}, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} +*/ + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_Valid_FieldValue(t *testing.T) { + + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if retCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) + +} + +/* API to test edit config with invalid field value. */ +func TestValidateEditConfig_Create_Syntax_CableLength(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "CABLE_LENGTH|AZURE", + map[string]string{ + "Ethernet8": "5m", + "Ethernet12": "5m", + "Ethernet16": "5m", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +/* API to test edit config with invalid field value. */ +func TestValidateEditConfig_Create_Syntax_Invalid_FieldValue(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{cvl.VALIDATE_ALL, cvl.OP_CREATE, "ACL_TABLE|TestACL1", map[string]string{ + "stage": "INGRESS", + "type": "junk", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_Invalid_PacketAction_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD777", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + unloadConfigDB(rclient, depDataMap) + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_Invalid_SrcPrefix_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/3288888", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvl.ValidationSessClose(cvSess) + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + unloadConfigDB(rclient, depDataMap) + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_InvalidIPAddress_Negative(t *testing.T) { + + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1a.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + unloadConfigDB(rclient, depDataMap) + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_OutofBound_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "19099090909090", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + unloadConfigDB(rclient, depDataMap) + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_InvalidProtocol_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "10388888", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} + +/* API to test edit config with valid syntax. */ +//Note: Syntax check is done first before dependency check +//hence ACL_TABLE is not required here +func TestValidateEditConfig_Create_Syntax_InvalidRange_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "777779000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + unloadConfigDB(rclient, depDataMap) + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_InvalidCharNEw_Negative(t *testing.T) { + + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1jjjj|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Syntax_SpecialChar_Positive(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule_1-2", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSessNew, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSessNew.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSessNew) + + if err != cvl.CVL_SUCCESS { //Should succeed + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) + +} + +func TestValidateEditConfig_Create_Syntax_InvalidKeyName_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "AC&&***L_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Semantic_AdditionalInvalidNode_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL1": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + "extra": "shhs", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} + +func TestValidateEditConfig_Create_Semantic_MissingMandatoryNode_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan101", + map[string]string{ + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Create_Syntax_Invalid_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULERule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Syntax_IncompleteKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Syntax_InvalidKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +/* +func TestValidateEditConfig_Update_Syntax_DependentData_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "MIRROR_SESSION|everflow", + map[string]string{ + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|MyACL11_ACL_IPV4|RULE_1", + map[string]string{ + "MIRROR_ACTION": "everflow", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrObj, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrObj)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrObj) + } + +} + +func TestValidateEditConfig_Create_Syntax_DependentData_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL|ch1", + map[string]string{ + "admin_status": "up", + "mtu": "9100", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL|ch2", + map[string]string{ + "admin_status": "up", + "mtu": "9100", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch1|Ethernet4", + map[string]string{}, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch1|Ethernet8", + map[string]string{}, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch2|Ethernet12", + map[string]string{}, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch2|Ethernet16", + map[string]string{}, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch2|Ethernet20", + map[string]string{}, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string{ + "vlanid": "102", + "members@": "Ethernet24,ch1,Ethernet8", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} +*/ + +func TestValidateEditConfig_Delete_Syntax_InvalidKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Update_Syntax_InvalidKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Delete_InvalidKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|TestACL1:Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrObj, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrObj)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrObj) + } + +} + +func TestValidateEditConfig_Update_Semantic_Invalid_Key_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|TestACL1Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103uuuu", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Delete_Semantic_Positive(t *testing.T) { + depDataMap := map[string]interface{}{ + "MIRROR_SESSION": map[string]interface{}{ + "everflow": map[string]interface{}{ + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "MIRROR_SESSION|everflow", + map[string]string{}, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) + +} + +func TestValidateEditConfig_Delete_Semantic_KeyNotExisting_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "MIRROR_SESSION|everflow0", + map[string]string{}, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Update_Semantic_MissingKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|TestACL177|Rule1", + map[string]string{ + "MIRROR_ACTION": "everflow", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Duplicate_Key_Negative(t *testing.T) { + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{} { + "TestACL100": map[string]interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + //Load same key in DB + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_TABLE|TestACL100", + map[string]string{ + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if retCode == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Update_Semantic_Positive(t *testing.T) { + + // Create ACL Table. + fileName := "testdata/create_acl_table.json" + aclTableMapByte, err := ioutil.ReadFile(fileName) + if err != nil { + fmt.Printf("read file %v err: %v", fileName, err) + } + + mpi_acl_table_map := loadConfig("", aclTableMapByte) + loadConfigDB(rclient, mpi_acl_table_map) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_TABLE|TestACL1", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if retCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, mpi_acl_table_map) + +} + +/* API to test edit config with valid syntax. */ +func TestValidateConfig_Update_Semantic_Vlan_Negative(t *testing.T) { + + cvSess, _ := cvl.ValidationSessOpen() + + jsonData := `{ + "VLAN": { + "Vlan100": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "107" + } + } + }` + + err := cvSess.ValidateConfig(jsonData) + + if err == cvl.CVL_SUCCESS { //Expected semantic failure + t.Errorf("Config Validation failed -- error details.") + } + + cvl.ValidationSessClose(cvSess) +} + +func TestValidateEditConfig_Update_Syntax_DependentData_Redis_Positive(t *testing.T) { + + // Create ACL Table. + fileName := "testdata/create_acl_table13.json" + aclTableMapByte, err := ioutil.ReadFile(fileName) + if err != nil { + fmt.Printf("read file %v err: %v", fileName, err) + } + + mpi_acl_table_map := loadConfig("", aclTableMapByte) + loadConfigDB(rclient, mpi_acl_table_map) + + // Create ACL Rule. + fileName = "testdata/acl_rule.json" + aclTableMapRule, err := ioutil.ReadFile(fileName) + if err != nil { + fmt.Printf("read file %v err: %v", fileName, err) + } + + mpi_acl_table_rule := loadConfig("", aclTableMapRule) + loadConfigDB(rclient, mpi_acl_table_rule) + + depDataMap := map[string]interface{}{ + /* Use MIRROR session once supported --- + "MIRROR_SESSION": map[string]interface{}{ + "everflow2": map[string]interface{}{ + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2", + }, + }, + */ + } + + loadConfigDB(rclient, depDataMap) + + /* ACL and Rule name pre-created . */ + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|TestACL13|Rule1", + map[string]string{ + /* Use Mirror session when supported + "MIRROR_ACTION": "everflow2", + */ + "PACKET_ACTION" : "FORWARD", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if retCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, mpi_acl_table_map) + unloadConfigDB(rclient, mpi_acl_table_rule) + unloadConfigDB(rclient, depDataMap) + +} + +func TestValidateEditConfig_Update_Syntax_DependentData_Invalid_Op_Seq(t *testing.T) { + + /* ACL and Rule name pre-created . */ + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_CREATE, + "ACL_TABLE|TestACL1", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "DROP", + "L4_SRC_PORT": "781", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err == cvl.CVL_SUCCESS { //Validation should fail + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Update_Syntax_DependentData_Redis_Negative(t *testing.T) { + + /* ACL does not exist.*/ + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "MIRROR_ACTION": "everflow0", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +/* Create with User provided dependent data. */ +func TestValidateEditConfig_Create_Syntax_DependentData_Redis_Positive(t *testing.T) { + + /* ACL and Rule name pre-created . */ + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_TABLE|TestACL22", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + cfgData = []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL22|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvlErrInfo, err = cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +/* Delete Non-Existing Key.*/ +func TestValidateEditConfig_Delete_Semantic_ACLTableReference_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|MyACL11_ACL_IPV4|RULE_1", + map[string]string{}, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Dependent_CacheData(t *testing.T) { + + cvSess, _ := cvl.ValidationSessOpen() + + //Create ACL rule + cfgDataAcl := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_TABLE|TestACL14", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + cvlErrInfo, err1 := cvSess.ValidateEditConfig(cfgDataAcl) + + //Create ACL rule + cfgDataRule := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL14|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvlErrInfo, err2 := cvSess.ValidateEditConfig(cfgDataRule) + + if err1 != cvl.CVL_SUCCESS || err2 != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + cvl.ValidationSessClose(cvSess) +} + +func TestValidateEditConfig_Create_DepData_In_MultiSess(t *testing.T) { + + //Create ACL rule - Session 1 + cvSess, _ := cvl.ValidationSessOpen() + cfgDataAcl := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_TABLE|TestACL16", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + cvlErrInfo, err1 := cvSess.ValidateEditConfig(cfgDataAcl) + + cvl.ValidationSessClose(cvSess) + + //Create ACL rule - Session 2, validation should fail + cvSess, _ = cvl.ValidationSessOpen() + cfgDataRule := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL16|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + _, err2 := cvSess.ValidateEditConfig(cfgDataRule) + + + cvl.ValidationSessClose(cvSess) + + if err1 != cvl.CVL_SUCCESS || err2 == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_DepData_From_Redis_Negative11(t *testing.T) { + + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{}{ + "TestACL1": map[string]interface{}{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + //Create ACL rule - Session 2 + cvSess, _ := cvl.ValidationSessOpen() + cfgDataRule := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL188|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgDataRule) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + + cvl.ValidationSessClose(cvSess) + + if (err != cvl.CVL_SEMANTIC_DEPENDENT_DATA_MISSING) { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} + +func TestValidateEditConfig_Create_DepData_From_Redis(t *testing.T) { + + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{}{ + "TestACL1": map[string]interface{}{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + //Create ACL rule - Session 2 + cvSess, _ := cvl.ValidationSessOpen() + cfgDataRule := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgDataRule) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} + +func TestValidateEditConfig_Create_Syntax_ErrAppTag_In_Range_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan701", + map[string]string{ + "vlanid": "7001", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + /* Compare expected error details and error tag. */ + if compareErrorDetails(cvlErrInfo, cvl.CVL_SYNTAX_ERROR, "vlanid-invalid", "") != true { + t.Errorf("Config Validation failed -- error details %v %v", cvlErrInfo, retCode) + } + +} + +func TestValidateEditConfig_Create_Syntax_ErrAppTag_In_Length_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_TABLE|TestACL1", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + "policy_desc": "A12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + /* Compare expected error details and error tag. */ + if compareErrorDetails(cvlErrInfo, cvl.CVL_SYNTAX_ERROR, "policy-desc-invalid-length", "") != true { + t.Errorf("Config Validation failed -- error details %v %v", cvlErrInfo, retCode) + } + +} + +func TestValidateEditConfig_Create_Syntax_ErrAppTag_In_Pattern_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan5001", + map[string]string{ + "vlanid": "102", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + /* Compare expected error details and error tag. */ + if compareErrorDetails(cvlErrInfo, cvl.CVL_SYNTAX_ERROR, "vlan-name-invalid", "") != true { + t.Errorf("Config Validation failed -- error details %v %v", cvlErrInfo, retCode) + } + +} + +func TestValidateEditConfig_Create_ErrAppTag_In_Must_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string{ + "vlanid": "102", + "members@": "Ethernet24,Ethernet8", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + /* Compare expected error details and error tag. */ + if compareErrorDetails(cvlErrInfo, cvl.CVL_SEMANTIC_DEPENDENT_DATA_MISSING ,"vlan-invalid", "") != true { + t.Errorf("Config Validation failed -- error details %v %v", cvlErrInfo, retCode) + } + +} + +/* API to test edit config with valid syntax. */ +func TestValidateEditConfig_Create_Syntax_InValid_FieldValue(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_UPDATE, + "ACL_TABLE|TestACL1", + map[string]string{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_NONE, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|TestACL1", + map[string]string{}, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if retCode == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +//EditConfig(Create) with dependent data from redis +func TestValidateEditConfig_Create_DepData_From_Redis_Negative(t *testing.T) { + + depDataMap := map[string]interface{} { + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgDataRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL2|Rule1", + map[string]string { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgDataRule) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { //should not succeed + t.Errorf("Config Validation should fail.") + } + + unloadConfigDB(rclient, depDataMap) +} + +// EditConfig(Create) with chained leafref from redis +func TestValidateEditConfig_Create_Chained_Leafref_DepData_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "VLAN" : map[string]interface{} { + "Vlan100": map[string]interface{} { + "members@": "Ethernet1", + "vlanid": "100", + }, + }, + "PORT" : map[string]interface{} { + "Ethernet1" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + "index": "1", + }, + "Ethernet2" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "85,86,87,89", + "mtu": "9100", + "index": "2", + }, + }, + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + "ports@":"Ethernet2", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + cfgDataVlan := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN_MEMBER|Vlan100|Ethernet1", + map[string]string { + "tagging_mode" : "tagged", + }, + }, + } + + _, err := cvSess.ValidateEditConfig(cfgDataVlan) + + if err != cvl.CVL_SUCCESS { //should succeed + t.Errorf("Config Validation failed.") + return + } + + cfgDataAclRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + + _, err = cvSess.ValidateEditConfig(cfgDataAclRule) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { //should succeed + t.Errorf("Config Validation failed.") + } + + unloadConfigDB(rclient, depDataMap) +} + +//EditConfig(Delete) deleting entry already used by other table as leafref +func TestValidateEditConfig_Delete_Dep_Leafref_Negative(t *testing.T) { + depDataMap := map[string]interface{} { + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + "ACL_RULE": map[string]interface{} { + "TestACL1|Rule1": map[string] interface{} { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cfgDataVlan := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_TABLE|TestACL1", + map[string]string { + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgDataVlan) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err == cvl.CVL_SUCCESS { //should be semantic failure + t.Errorf("Config Validation failed.") + } + + unloadConfigDB(rclient, depDataMap) +} + +//EditConfig(Create) with chained leafref from redis +func TestValidateEditConfig_Create_Chained_Leafref_DepData_Negative(t *testing.T) { + depDataMap := map[string]interface{} { + "PORT" : map[string]interface{} { + "Ethernet3" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + "index": "3", + }, + "Ethernet5" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "85,86,87,89", + "mtu": "9100", + "index": "5", + }, + }, + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + "ports@":"Ethernet2", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cfgDataAclRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + _, err := cvSess.ValidateEditConfig(cfgDataAclRule) + + cvl.ValidationSessClose(cvSess) + + if err == cvl.CVL_SUCCESS { //should not succeed + t.Errorf("Config Validation failed.") + } + + unloadConfigDB(rclient, depDataMap) +} +func TestValidateEditConfig_Create_Syntax_InvalidVlanRange_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan5002", + map[string]string{ + "vlanid": "6002", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, retCode := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if retCode == cvl.CVL_SUCCESS { //should not succeed + t.Errorf("Config Validation failed with details %v.", cvlErrInfo) + } + +} + +//Test Initialize() API +func TestLogging(t *testing.T) { + ret := cvl.Initialize() + str := "Testing" + cvl.CVL_LOG(INFO ,"This is Info Log %s", str) + cvl.CVL_LOG(WARNING,"This is Warning Log %s", str) + cvl.CVL_LOG(ERROR ,"This is Error Log %s", str) + cvl.CVL_LOG(INFO_API ,"This is Info API %s", str) + cvl.CVL_LOG(INFO_TRACE ,"This is Info Trace %s", str) + cvl.CVL_LOG(INFO_DEBUG ,"This is Info Debug %s", str) + cvl.CVL_LOG(INFO_DATA ,"This is Info Data %s", str) + cvl.CVL_LOG(INFO_DETAIL ,"This is Info Detail %s", str) + cvl.CVL_LOG(INFO_ALL ,"This is Info all %s", str) + + if (ret != cvl.CVL_SUCCESS) { + t.Errorf("CVl initialization failed") + } + + cvl.Finish() + + //Initialize again for other test cases to run + cvl.Initialize() +} + +func TestValidateEditConfig_DepData_Through_Cache(t *testing.T) { + depDataMap := map[string]interface{} { + "PORT" : map[string]interface{} { + "Ethernet3" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + "index": "3", + }, + "Ethernet5" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "85,86,87,89", + "mtu": "9100", + "index": "5", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + //Modify entry + depDataMap = map[string]interface{} { + "PORT" : map[string]interface{} { + "Ethernet3" : map[string]interface{} { + "mtu": "9200", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + cfgDataAclRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_TABLE|TestACL1", + map[string]string { + "stage": "INGRESS", + "type": "L3", + "ports@":"Ethernet3,Ethernet5", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + _, err := cvSess.ValidateEditConfig(cfgDataAclRule) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { //should succeed + t.Errorf("Config Validation failed.") + } + + unloadConfigDB(rclient, depDataMap) +} + +/* Delete field for an existing key.*/ +func TestValidateEditConfig_Delete_Single_Field_Positive(t *testing.T) { + + depDataMap := map[string]interface{} { + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + "policy_desc":"Test ACL desc", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_TABLE|TestACL1", + map[string]string{ + "policy_desc":"Test ACL desc", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + + unloadConfigDB(rclient, depDataMap) +} + +func TestValidateConfig_Repeated_Keys_Positive(t *testing.T) { + jsonData := `{ + "WRED_PROFILE": { + "AZURE_LOSSLESS": { + "red_max_threshold": "312000", + "wred_green_enable": "true", + "ecn": "ecn_all", + "green_min_threshold": "104000", + "red_min_threshold": "104000", + "wred_yellow_enable": "true", + "yellow_min_threshold": "104000", + "wred_red_enable": "true", + "yellow_max_threshold": "312000", + "green_max_threshold": "312000" + } + }, + "SCHEDULER": { + "scheduler.0": { + "type": "DWRR", + "weight": "25" + }, + "scheduler.1": { + "type": "DWRR", + "weight": "30" + }, + "scheduler.2": { + "type": "DWRR", + "weight": "20" + } + }, + "QUEUE": { + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|0": { + "scheduler": "[SCHEDULER|scheduler.1]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|1": { + "scheduler": "[SCHEDULER|scheduler.2]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|3-4": { + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]", + "scheduler": "[SCHEDULER|scheduler.0]" + } + } + }` + + cvSess, _ := cvl.ValidationSessOpen() + err := cvSess.ValidateConfig(jsonData) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details.") + } + + cvl.ValidationSessClose(cvSess) +} + +func TestValidateEditConfig_Delete_Entry_Then_Dep_Leafref_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "VLAN" : map[string]interface{} { + "Vlan20": map[string] interface{} { + "vlanid": "20", + }, + }, + "VLAN_MEMBER": map[string]interface{} { + "Vlan20|Ethernet4": map[string] interface{} { + "tagging_mode": "tagged", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + cfgDataAcl := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "VLAN_MEMBER|Vlan20|Ethernet4", + map[string]string { + }, + }, + } + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgDataAcl) + + cfgDataAcl = []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_DELETE, + "VLAN_MEMBER|Vlan20|Ethernet4", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "VLAN|Vlan20", + map[string]string { + }, + }, + } + + cvlErrInfo, err = cvSess.ValidateEditConfig(cfgDataAcl) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err != cvl.CVL_SUCCESS { //should be success + t.Errorf("Config Validation failed.") + } + + unloadConfigDB(rclient, depDataMap) +} + +func TestBadSchema(t *testing.T) { + env := os.Environ() + env[0] = env[0] + " " + + if _, err := os.Stat("/usr/sbin/schema"); os.IsNotExist(err) { + //Corrupt some schema file + exec.Command("/bin/sh", "-c", "/bin/cp testdata/schema/sonic-port.yin testdata/schema/sonic-port.yin.bad" + + " && /bin/sed -i '1 a ' testdata/schema/sonic-port.yin.bad").Output() + + //Parse bad schema file + if module, _ := yparser.ParseSchemaFile("testdata/schema/sonic-port.yin.bad"); module != nil { //should fail + t.Errorf("Bad schema parsing should fail.") + } + + //Revert to + exec.Command("/bin/sh", "-c", "/bin/rm testdata/schema/sonic-port.yin.bad").Output() + } else { + //Corrupt some schema file + exec.Command("/bin/sh", "-c", "/bin/cp /usr/sbin/schema/sonic-port.yin /usr/sbin/schema/sonic-port.yin.bad" + + " && /bin/sed -i '1 a ' /usr/sbin/schema/sonic-port.yin.bad").Output() + + //Parse bad schema file + if module, _ := yparser.ParseSchemaFile("/usr/sbin/schema/sonic-port.yin.bad"); module != nil { //should fail + t.Errorf("Bad schema parsing should fail.") + } + + //Revert to + exec.Command("/bin/sh", "-c", "/bin/rm /usr/sbin/schema/sonic-port.yin.bad").Output() + } + +} + + +func TestServicability_Debug_Trace(t *testing.T) { + + cvl.Debug(false) + SetTrace(false) + + //Reload the config file by sending SIGUSR2 to ourself + p, err := os.FindProcess(os.Getpid()) + if (err == nil) { + p.Signal(syscall.SIGUSR2) + } + + + depDataMap := map[string]interface{}{ + "ACL_TABLE": map[string]interface{}{ + "TestACL1": map[string]interface{}{ + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + loadConfigDB(rclient, depDataMap) + + //Create ACL rule - Session 2 + cvSess, _ := cvl.ValidationSessOpen() + cfgDataRule := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + + cvSess.ValidateEditConfig(cfgDataRule) + + unloadConfigDB(rclient, depDataMap) + + SetTrace(true) + cvl.Debug(true) + + cvl.ValidationSessClose(cvSess) + + //Reload the bad config file by sending SIGUSR2 to ourself + exec.Command("/bin/sh", "-c", "/bin/cp conf/cvl_cfg.json conf/cvl_cfg.json.orig" + + " && /bin/echo 'junk' >> conf/cvl_cfg.json").Output() + p, err = os.FindProcess(os.Getpid()) + if (err == nil) { + p.Signal(syscall.SIGUSR2) + } + exec.Command("/bin/sh", "-c", "/bin/mv conf/cvl_cfg.json.orig conf/cvl_cfg.json").Output() +} + +// EditConfig(Create) with chained leafref from redis +func TestValidateEditConfig_Delete_Create_Same_Entry_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "VLAN" : map[string]interface{} { + "Vlan100": map[string]interface{} { + "members@": "Ethernet1", + "vlanid": "100", + }, + }, + "PORT" : map[string]interface{} { + "Ethernet1" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + "index": "1", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + cfgDataVlan := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "VLAN|Vlan100", + map[string]string { + }, + }, + } + + _, err1 := cvSess.ValidateEditConfig(cfgDataVlan) + + //Same entry getting created again + cfgDataVlan = []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan100", + map[string]string { + "vlanid": "100", + }, + }, + } + + _, err2 := cvSess.ValidateEditConfig(cfgDataVlan) + + if err1 != cvl.CVL_SUCCESS || err2 != cvl.CVL_SUCCESS { //should succeed + t.Errorf("Config Validation failed.") + return + } + + + cvl.ValidationSessClose(cvSess) + + unloadConfigDB(rclient, depDataMap) +} + +func TestValidateStartupConfig_Positive(t *testing.T) { + cvSess, _ := cvl.ValidationSessOpen() + if cvl.CVL_NOT_IMPLEMENTED != cvSess.ValidateStartupConfig("") { + t.Errorf("Not implemented yet.") + } + cvl.ValidationSessClose(cvSess) +} + +func TestValidateIncrementalConfig_Positive(t *testing.T) { + existingDataMap := map[string]interface{} { + "VLAN" : map[string]interface{} { + "Vlan800": map[string]interface{} { + "members@": "Ethernet1", + "vlanid": "800", + }, + "Vlan801": map[string]interface{} { + "members@": "Ethernet2", + "vlanid": "801", + }, + }, + "VLAN_MEMBER": map[string]interface{} { + "Vlan800|Ethernet1": map[string] interface{} { + "tagging_mode": "tagged", + }, + }, + "PORT" : map[string]interface{} { + "Ethernet1" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "81,82,83,84", + "mtu": "9100", + "index": "1", + }, + "Ethernet2" : map[string]interface{} { + "alias":"hundredGigE1", + "lanes": "85,86,87,89", + "mtu": "9100", + "index": "2", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, existingDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + jsonData := `{ + "VLAN": { + "Vlan800": { + "members": [ + "Ethernet1", + "Ethernet2" + ], + "vlanid": "800" + } + }, + "VLAN_MEMBER": { + "Vlan800|Ethernet1": { + "tagging_mode": "untagged" + }, + "Vlan801|Ethernet2": { + "tagging_mode": "tagged" + } + } + }` + + ret := cvSess.ValidateIncrementalConfig(jsonData) + + cvl.ValidationSessClose(cvSess) + + unloadConfigDB(rclient, existingDataMap) + + if ret != cvl.CVL_SUCCESS { //should succeed + t.Errorf("Config Validation failed.") + return + } +} + +//Validate key only +func TestValidateKeys(t *testing.T) { + cvSess, _ := cvl.ValidationSessOpen() + if cvl.CVL_NOT_IMPLEMENTED != cvSess.ValidateKeys([]string{}) { + t.Errorf("Not implemented yet.") + } + cvl.ValidationSessClose(cvSess) +} + +//Validate key and data +func TestValidateKeyData(t *testing.T) { + cvSess, _ := cvl.ValidationSessOpen() + if cvl.CVL_NOT_IMPLEMENTED != cvSess.ValidateKeyData("", "") { + t.Errorf("Not implemented yet.") + } + cvl.ValidationSessClose(cvSess) +} + +//Validate key, field and value +func TestValidateFields(t *testing.T) { + cvSess, _ := cvl.ValidationSessOpen() + if cvl.CVL_NOT_IMPLEMENTED != cvSess.ValidateFields("", "", "") { + t.Errorf("Not implemented yet.") + } + cvl.ValidationSessClose(cvSess) +} + +func TestValidateEditConfig_Two_Updates_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "ACL_TABLE" : map[string]interface{} { + "TestACL1": map[string] interface{} { + "stage": "INGRESS", + "type": "L3", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + cfgDataAcl := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_TABLE|TestACL1", + map[string]string { + "policy_desc": "Test ACL", + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_TABLE|TestACL1", + map[string]string { + "type": "MIRROR", + }, + }, + } + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgDataAcl) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err != cvl.CVL_SUCCESS { //should be success + t.Errorf("Config Validation failed.") + } + + unloadConfigDB(rclient, depDataMap) + +} +func TestValidateEditConfig_Create_Syntax_DependentData_PositivePortChannel(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string{ + "vlanid": "1001", + "members@": "Ethernet28,PortChannel002", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + + +func TestValidateEditConfig_Create_Syntax_DependentData_PositivePortChannelIfName(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string{ + "vlanid": "1001", + "members@": "Ethernet24,PortChannel001", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, err := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if err != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} + +func TestValidateEditConfig_Create_Syntax_DependentData_NegativePortChannelEthernet(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string{ + "vlanid": "1001", + "members@": "PortChannel001,Ethernet4", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Create_Syntax_DependentData_NegativePortChannelNew(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string{ + "vlanid": "1001", + "members@": "Ethernet12,PortChannel001", + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Use_Updated_Data_As_Create_DependentData_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "VLAN" : map[string]interface{} { + "Vlan201": map[string] interface{} { + "vlanid": "201", + "members@": "Ethernet8", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "VLAN|Vlan201", + map[string]string{ + "members@": "Ethernet8,Ethernet12", + }, + }, + } + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + if cvlErrInfo.ErrCode != cvl.CVL_SUCCESS { + unloadConfigDB(rclient, depDataMap) + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + return + } + + cfgData = []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN_MEMBER|Vlan201|Ethernet8", + map[string]string{ + "tagging_mode": "tagged", + }, + }, + } + + cvlErrInfo, _ = cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + unloadConfigDB(rclient, depDataMap) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Use_Updated_Data_As_Create_DependentData_Single_Call_Positive(t *testing.T) { + depDataMap := map[string]interface{} { + "VLAN" : map[string]interface{} { + "Vlan201": map[string] interface{} { + "vlanid": "201", + "members@": "Ethernet8", + }, + }, + } + + //Prepare data in Redis + loadConfigDB(rclient, depDataMap) + + cvSess, _ := cvl.ValidationSessOpen() + + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "VLAN|Vlan201", + map[string]string{ + "members@": "Ethernet8,Ethernet12", + }, + }, + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN_MEMBER|Vlan201|Ethernet8", + map[string]string{ + "tagging_mode": "tagged", + }, + }, + } + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + unloadConfigDB(rclient, depDataMap) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Create_Syntax_Interface_AllKeys_Positive(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "INTERFACE|Ethernet24|10.0.0.0/31", + map[string]string{ + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Create_Syntax_Interface_OptionalKey_Positive(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "INTERFACE|Ethernet24", + map[string]string{ + /*"vrf-name": "Vrf1", -- Enable once VRF YANG implemented */ + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_Create_Syntax_Interface_IncorrectKey_Negative(t *testing.T) { + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "INTERFACE|10.0.0.0/31", + map[string]string{ + }, + }, + } + + cvSess, _ := cvl.ValidationSessOpen() + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode == cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } +} + +func TestValidateEditConfig_EmptyNode_Positive(t *testing.T) { + cvSess, _ := cvl.ValidationSessOpen() + + + cfgData := []cvl.CVLEditConfigData{ + cvl.CVLEditConfigData{ + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "PORT|Ethernet0", + map[string]string{ + "description": "", + "index": "3", + }, + }, + } + + cvlErrInfo, _ := cvSess.ValidateEditConfig(cfgData) + + cvl.ValidationSessClose(cvSess) + + WriteToFile(fmt.Sprintf("\nCVL Error Info is %v\n", cvlErrInfo)) + + if cvlErrInfo.ErrCode != cvl.CVL_SUCCESS { + t.Errorf("Config Validation failed -- error details %v", cvlErrInfo) + } + +} diff --git a/cvl/internal/util/util.go b/cvl/internal/util/util.go new file mode 100644 index 000000000..7404500d0 --- /dev/null +++ b/cvl/internal/util/util.go @@ -0,0 +1,255 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package util + +import ( + "os" + "fmt" + "runtime" + "encoding/json" + "io/ioutil" + "os/signal" + "syscall" + "strings" + "flag" + log "github.com/golang/glog" +) + +var CVL_SCHEMA string = "schema/" +var CVL_CFG_FILE string = "/usr/sbin/cvl_cfg.json" + +//package init function +func init() { + if (os.Getenv("CVL_SCHEMA_PATH") != "") { + CVL_SCHEMA = os.Getenv("CVL_SCHEMA_PATH") + "/" + } + + if (os.Getenv("CVL_CFG_FILE") != "") { + CVL_CFG_FILE = os.Getenv("CVL_CFG_FILE") + } +} + +var cvlCfgMap map[string]string + +/* Logging Level for CVL global logging. */ +type CVLLogLevel uint8 +const ( + INFO = 0 + iota + WARNING + ERROR + FATAL + INFO_API + INFO_TRACE + INFO_DEBUG + INFO_DATA + INFO_DETAIL + INFO_ALL +) + +var cvlTraceFlags uint32 + +/* Logging levels for CVL Tracing. */ +type CVLTraceLevel uint32 +const ( + TRACE_MIN = 0 + TRACE_MAX = 8 + TRACE_CACHE = 1 << TRACE_MIN + TRACE_LIBYANG = 1 << 1 + TRACE_YPARSER = 1 << 2 + TRACE_CREATE = 1 << 3 + TRACE_UPDATE = 1 << 4 + TRACE_DELETE = 1 << 5 + TRACE_SEMANTIC = 1 << 6 + TRACE_ONERROR = 1 << 7 + TRACE_SYNTAX = 1 << TRACE_MAX + +) + + +var traceLevelMap = map[int]string { + /* Caching operation traces */ + TRACE_CACHE : "TRACE_CACHE", + /* Libyang library traces. */ + TRACE_LIBYANG: "TRACE_LIBYANG", + /* Yang Parser traces. */ + TRACE_YPARSER : "TRACE_YPARSER", + /* Create operation traces. */ + TRACE_CREATE : "TRACE_CREATE", + /* Update operation traces. */ + TRACE_UPDATE : "TRACE_UPDATE", + /* Delete operation traces. */ + TRACE_DELETE : "TRACE_DELETE", + /* Semantic Validation traces. */ + TRACE_SEMANTIC : "TRACE_SEMANTIC", + /* Syntax Validation traces. */ + TRACE_SYNTAX : "TRACE_SYNTAX", + /* Trace on Error. */ + TRACE_ONERROR : "TRACE_ONERROR", +} + +var Tracing bool = false + +var traceFlags uint16 = 0 + +func SetTrace(on bool) { + if (on == true) { + Tracing = true + traceFlags = 1 + } else { + Tracing = false + traceFlags = 0 + } +} + +func IsTraceSet() bool { + if (traceFlags == 0) { + return false + } else { + return true + } +} + +func TRACE_LEVEL_LOG(level log.Level, tracelevel CVLTraceLevel, fmtStr string, args ...interface{}) { + + if (IsTraceSet() == false) { + return + } + + level = (level - INFO_API) + 1; + + traceEnabled := false + if ((cvlTraceFlags & (uint32)(tracelevel)) != 0) { + traceEnabled = true + } + + if IsTraceSet() == true && traceEnabled == true { + pc := make([]uintptr, 10) + runtime.Callers(2, pc) + f := runtime.FuncForPC(pc[0]) + file, line := f.FileLine(pc[0]) + + fmt.Printf("%s:%d %s(): ", file, line, f.Name()) + fmt.Printf(fmtStr+"\n", args...) + } else { + if (traceEnabled == true) { + log.V(level).Infof(fmtStr, args...) + } + } +} + +func CVL_LEVEL_LOG(level CVLLogLevel, format string, args ...interface{}) { + + switch level { + case INFO: + log.Infof(format, args...) + case WARNING: + log.Warningf(format, args...) + case ERROR: + log.Errorf(format, args...) + case FATAL: + log.Fatalf(format, args...) + case INFO_API: + log.V(1).Infof(format, args...) + case INFO_TRACE: + log.V(2).Infof(format, args...) + case INFO_DEBUG: + log.V(3).Infof(format, args...) + case INFO_DATA: + log.V(4).Infof(format, args...) + case INFO_DETAIL: + log.V(5).Infof(format, args...) + case INFO_ALL: + log.V(6).Infof(format, args...) + } + +} + + +func ConfigFileSyncHandler() { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGUSR2) + go func() { + for { + <-sigs + cvlCfgMap := ReadConfFile() + + if cvlCfgMap == nil { + return + } + + CVL_LEVEL_LOG(INFO ,"Received SIGUSR2. Changed configuration values are %v", cvlCfgMap) + + + if (strings.Compare(cvlCfgMap["LOGTOSTDERR"], "true") == 0) { + SetTrace(true) + flag.Set("logtostderr", "true") + flag.Set("stderrthreshold", cvlCfgMap["STDERRTHRESHOLD"]) + flag.Set("v", cvlCfgMap["VERBOSITY"]) + } + } + }() + +} + +func ReadConfFile() map[string]string{ + + /* Return if CVL configuration file is not present. */ + if _, err := os.Stat(CVL_CFG_FILE); os.IsNotExist(err) { + return nil + } + + data, err := ioutil.ReadFile(CVL_CFG_FILE) + + err = json.Unmarshal(data, &cvlCfgMap) + + if err != nil { + CVL_LEVEL_LOG(INFO ,"Error in reading cvl configuration file %v", err) + return nil + } + + CVL_LEVEL_LOG(INFO ,"Current Values of CVL Configuration File %v", cvlCfgMap) + var index uint32 + + for index = TRACE_MIN ; index < TRACE_MAX ; index++ { + if (strings.Compare(cvlCfgMap[traceLevelMap[1 << index]], "true") == 0) { + cvlTraceFlags = cvlTraceFlags | (1 << index) + } + } + + return cvlCfgMap +} + +func SkipValidation() bool { + val, existing := cvlCfgMap["SKIP_VALIDATION"] + if (existing == true) && (val == "true") { + return true + } + + return false +} + +func SkipSemanticValidation() bool { + val, existing := cvlCfgMap["SKIP_SEMANTIC_VALIDATION"] + if (existing == true) && (val == "true") { + return true + } + + return false +} diff --git a/cvl/internal/yparser/yparser.go b/cvl/internal/yparser/yparser.go new file mode 100644 index 000000000..a35868fa4 --- /dev/null +++ b/cvl/internal/yparser/yparser.go @@ -0,0 +1,699 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package yparser + +/* Yang parser using libyang library */ + +import ( + "os" + "strings" + log "github.com/golang/glog" + . "github.com/Azure/sonic-mgmt-common/cvl/internal/util" +) + +/* +#cgo LDFLAGS: -lyang +#include +#include +#include +#include +#include + +struct lyd_node* lyd_parse_data_path(struct ly_ctx *ctx, const char *path, LYD_FORMAT format, int options) { + return lyd_parse_path(ctx, path, format, options); +} + +struct lyd_node *lyd_parse_data_mem(struct ly_ctx *ctx, const char *data, LYD_FORMAT format, int options) +{ + return lyd_parse_mem(ctx, data, format, options); +} + +int lyd_data_validate(struct lyd_node **node, int options, struct ly_ctx *ctx) +{ + return lyd_validate(node, options, ctx); +} + +int lyd_data_validate_all(const char *data, const char *depData, const char *othDepData, int options, struct ly_ctx *ctx) +{ + struct lyd_node *pData; + struct lyd_node *pDepData; + struct lyd_node *pOthDepData; + + if ((data == NULL) || (data[0] == '\0')) + { + return -1; + } + + pData = lyd_parse_mem(ctx, data, LYD_XML, LYD_OPT_EDIT | LYD_OPT_NOEXTDEPS); + if (pData == NULL) + { + return -1; + } + + if ((depData != NULL) && (depData[0] != '\0')) + { + if (NULL != (pDepData = lyd_parse_mem(ctx, depData, LYD_XML, LYD_OPT_EDIT | LYD_OPT_NOEXTDEPS))) + { + if (0 != lyd_merge_to_ctx(&pData, pDepData, LYD_OPT_DESTRUCT, ctx)) + { + return -1; + } + } + } + + if ((othDepData != NULL) && (othDepData[0] != '\0')) + { + if (NULL != (pOthDepData = lyd_parse_mem(ctx, othDepData, LYD_XML, LYD_OPT_EDIT | LYD_OPT_NOEXTDEPS))) + { + if (0 != lyd_merge_to_ctx(&pData, pOthDepData, LYD_OPT_DESTRUCT, ctx)) + { + return -1; + } + } + } + + return lyd_validate(&pData, LYD_OPT_CONFIG, ctx); +} + +int lyd_multi_new_leaf(struct lyd_node *parent, const struct lys_module *module, const char *leafVal) +{ + char s[4048]; + char *name, *val, *saveptr; + + strcpy(s, leafVal); + + name = strtok_r(s, "#", &saveptr); + + while (name != NULL) + { + val = strtok_r(NULL, "#", &saveptr); + if (val != NULL) + { + if (NULL == lyd_new_leaf(parent, module, name, val)) + { + return -1; + } + } + + name = strtok_r(NULL, "#", &saveptr); + } + + return 0; +} + +struct lyd_node *lyd_find_node(struct lyd_node *root, const char *xpath) +{ + struct ly_set *set = NULL; + struct lyd_node *node = NULL; + + if (root == NULL) + { + return NULL; + } + + set = lyd_find_path(root, xpath); + if (set == NULL || set->number == 0) { + return NULL; + } + + node = set->set.d[0]; + ly_set_free(set); + + return node; +} + +int lyd_change_leaf_data(struct lyd_node *leaf, const char *val_str) +{ + return lyd_change_leaf((struct lyd_node_leaf_list *)leaf, val_str); +} + +*/ +import "C" + +type YParserCtx C.struct_ly_ctx +type YParserNode C.struct_lyd_node +type YParserModule C.struct_lys_module + +var ypCtx *YParserCtx +var ypOpModule *YParserModule +var ypOpRoot *YParserNode //Operation root +var ypOpNode *YParserNode //Operation node + + +type YParser struct { + ctx *YParserCtx //Parser context + root *YParserNode //Top evel root for validation + operation string //Edit operation +} + +/* YParser Error Structure */ +type YParserError struct { + ErrCode YParserRetCode /* Error Code describing type of error. */ + Msg string /* Detailed error message. */ + ErrTxt string /* High level error message. */ + TableName string /* List/Table having error */ + Keys []string /* Keys of the Table having error. */ + Field string /* Field Name throwing error . */ + Value string /* Field Value throwing error */ + ErrAppTag string /* Error App Tag. */ +} + +type YParserRetCode int +const ( + YP_SUCCESS YParserRetCode = 1000 + iota + YP_SYNTAX_ERROR + YP_SEMANTIC_ERROR + YP_SYNTAX_MISSING_FIELD + YP_SYNTAX_INVALID_FIELD /* Invalid Field */ + YP_SYNTAX_INVALID_INPUT_DATA /*Invalid Input Data */ + YP_SYNTAX_MULTIPLE_INSTANCE /* Multiple Field Instances */ + YP_SYNTAX_DUPLICATE /* Duplicate Fields */ + YP_SYNTAX_ENUM_INVALID /* Invalid enum value */ + YP_SYNTAX_ENUM_INVALID_NAME /* Invalid enum name */ + YP_SYNTAX_ENUM_WHITESPACE /* Enum name with leading/trailing whitespaces */ + YP_SYNTAX_OUT_OF_RANGE /* Value out of range/length/pattern (data) */ + YP_SYNTAX_MINIMUM_INVALID /* min-elements constraint not honored */ + YP_SYNTAX_MAXIMUM_INVALID /* max-elements constraint not honored */ + YP_SEMANTIC_DEPENDENT_DATA_MISSING /* Dependent Data is missing */ + YP_SEMANTIC_MANDATORY_DATA_MISSING /* Mandatory Data is missing */ + YP_SEMANTIC_KEY_ALREADY_EXIST /* Key already existing */ + YP_SEMANTIC_KEY_NOT_EXIST /* Key is missing */ + YP_SEMANTIC_KEY_DUPLICATE /* Duplicate key */ + YP_SEMANTIC_KEY_INVALID /* Invalid key */ + YP_INTERNAL_UNKNOWN +) + +var yparserInitialized bool = false + +func TRACE_LOG(level log.Level, tracelevel CVLTraceLevel, fmtStr string, args ...interface{}) { + TRACE_LEVEL_LOG(level, tracelevel , fmtStr, args...) +} + +func CVL_LOG(level CVLLogLevel, fmtStr string, args ...interface{}) { + CVL_LEVEL_LOG(level, fmtStr, args...) +} + +//package init function +func init() { + if (os.Getenv("CVL_DEBUG") != "") { + Debug(true) + } +} + +func Debug(on bool) { + if (on == true) { + C.ly_verb(C.LY_LLDBG) + } else { + C.ly_verb(C.LY_LLERR) + } +} + +func Initialize() { + if (yparserInitialized != true) { + ypCtx = (*YParserCtx)(C.ly_ctx_new(C.CString(CVL_SCHEMA), 0)) + C.ly_verb(C.LY_LLERR) + // yparserInitialized = true + } +} + +func Finish() { + if (yparserInitialized == true) { + C.ly_ctx_destroy((*C.struct_ly_ctx)(ypCtx), nil) + // yparserInitialized = false + } +} + +//Parse YIN schema file +func ParseSchemaFile(modelFile string) (*YParserModule, YParserError) { + /* schema */ + TRACE_LOG(INFO_DEBUG, TRACE_YPARSER, "Parsing schema file %s ...\n", modelFile) + + module := C.lys_parse_path((*C.struct_ly_ctx)(ypCtx), C.CString(modelFile), C.LYS_IN_YIN) + if module == nil { + return nil, getErrorDetails() + } + + if (strings.Contains(modelFile, "sonic-common.yin") == true) { + ypOpModule = (*YParserModule)(module) + ypOpRoot = (*YParserNode)(C.lyd_new(nil, (*C.struct_lys_module)(ypOpModule), C.CString("operation"))) + ypOpNode = (*YParserNode)(C.lyd_new_leaf((*C.struct_lyd_node)(ypOpRoot), (*C.struct_lys_module)(ypOpModule), C.CString("operation"), C.CString("NOP"))) + } + + return (*YParserModule)(module), YParserError {ErrCode : YP_SUCCESS,} +} + +//Add child node to a parent node +func(yp *YParser) AddChildNode(module *YParserModule, parent *YParserNode, name string) *YParserNode { + + ret := (*YParserNode)(C.lyd_new((*C.struct_lyd_node)(parent), (*C.struct_lys_module)(module), C.CString(name))) + if (ret == nil) { + TRACE_LOG(INFO_DEBUG, TRACE_YPARSER, "Failed parsing node %s\n", name) + } + + return ret +} + +//Add child node to a parent node +func(yp *YParser) AddMultiLeafNodes(module *YParserModule, parent *YParserNode, multiLeaf string) YParserError { + if (0 != C.lyd_multi_new_leaf((*C.struct_lyd_node)(parent), (*C.struct_lys_module)(module), C.CString(multiLeaf))) { + if (Tracing == true) { + TRACE_LOG(INFO_API, TRACE_ONERROR, "Failed to create Multi Leaf Data = %v", multiLeaf) + } + return getErrorDetails() + } + + return YParserError {ErrCode : YP_SUCCESS,} + +} + +//Return entire subtree in XML format in string +func (yp *YParser) NodeDump(root *YParserNode) string { + if (root == nil) { + return "" + } else { + var outBuf *C.char + C.lyd_print_mem(&outBuf, (*C.struct_lyd_node)(root), C.LYD_XML, C.LYP_WITHSIBLINGS) + return C.GoString(outBuf) + } +} + +//Merge source with destination +func (yp *YParser) MergeSubtree(root, node *YParserNode) (*YParserNode, YParserError) { + rootTmp := (*C.struct_lyd_node)(root) + + if (root == nil || node == nil) { + return root, YParserError {ErrCode: YP_SUCCESS} + } + + if (Tracing == true) { + rootdumpStr := yp.NodeDump((*YParserNode)(rootTmp)) + TRACE_LOG(INFO_API, TRACE_YPARSER, "Root subtree = %v\n", rootdumpStr) + } + + if (0 != C.lyd_merge_to_ctx(&rootTmp, (*C.struct_lyd_node)(node), C.LYD_OPT_DESTRUCT, + (*C.struct_ly_ctx)(ypCtx))) { + return (*YParserNode)(rootTmp), getErrorDetails() + } + + if (Tracing == true) { + dumpStr := yp.NodeDump((*YParserNode)(rootTmp)) + TRACE_LOG(INFO_API, TRACE_YPARSER, "Merged subtree = %v\n", dumpStr) + } + + return (*YParserNode)(rootTmp), YParserError {ErrCode : YP_SUCCESS,} +} + +//Cache subtree +func (yp *YParser) CacheSubtree(dupSrc bool, node *YParserNode) YParserError { + rootTmp := (*C.struct_lyd_node)(yp.root) + var dup *C.struct_lyd_node + + if (node == nil) { + //nothing to merge + return YParserError {ErrCode : YP_SUCCESS,} + } + + if (dupSrc == true) { + dup = C.lyd_dup_withsiblings((*C.struct_lyd_node)(node), C.LYD_DUP_OPT_RECURSIVE | C.LYD_DUP_OPT_NO_ATTR) + } else { + dup = (*C.struct_lyd_node)(node) + } + + if (yp.root != nil) { + if (0 != C.lyd_merge_to_ctx(&rootTmp, (*C.struct_lyd_node)(dup), C.LYD_OPT_DESTRUCT, + (*C.struct_ly_ctx)(ypCtx))) { + return getErrorDetails() + } + } else { + yp.root = (*YParserNode)(dup) + } + + if (Tracing == true) { + dumpStr := yp.NodeDump((*YParserNode)(rootTmp)) + TRACE_LOG(INFO_API, TRACE_YPARSER, "Cached subtree = %v\n", dumpStr) + } + + return YParserError {ErrCode : YP_SUCCESS,} +} + +func (yp *YParser) DestroyCache() YParserError { + + if (yp.root != nil) { + C.lyd_free_withsiblings((*C.struct_lyd_node)(yp.root)) + yp.root = nil + } + + return YParserError {ErrCode : YP_SUCCESS,} +} + +//Set operation +func (yp *YParser) SetOperation(op string) YParserError { + if (ypOpNode == nil) { + return YParserError {ErrCode : YP_INTERNAL_UNKNOWN,} + } + + if (0 != C.lyd_change_leaf_data((*C.struct_lyd_node)(ypOpNode), C.CString(op))) { + return YParserError {ErrCode : YP_INTERNAL_UNKNOWN,} + } + + yp.operation = op + return YParserError {ErrCode : YP_SUCCESS,} +} + +//Validate config - syntax and semantics +func (yp *YParser) ValidateData(data, depData *YParserNode) YParserError { + + var dataRoot *YParserNode + + if (depData != nil) { + if dataRoot, _ = yp.MergeSubtree(data, depData); dataRoot == nil { + CVL_LOG(ERROR, "Failed to merge dependent data\n") + return getErrorDetails() + } + } + + dataRootTmp := (*C.struct_lyd_node)(dataRoot) + + if (0 != C.lyd_data_validate(&dataRootTmp, C.LYD_OPT_CONFIG, (*C.struct_ly_ctx)(ypCtx))) { + if (Tracing == true) { + strData := yp.NodeDump((*YParserNode)(dataRootTmp)) + TRACE_LOG(INFO_API, TRACE_ONERROR, "Failed to validate data = %v", strData) + } + + CVL_LOG(ERROR, "Validation failed\n") + return getErrorDetails() + } + + return YParserError {ErrCode : YP_SUCCESS,} +} + +//Perform syntax checks +func (yp *YParser) ValidateSyntax(data *YParserNode) YParserError { + dataTmp := (*C.struct_lyd_node)(data) + + //Just validate syntax + if (0 != C.lyd_data_validate(&dataTmp, C.LYD_OPT_EDIT | C.LYD_OPT_NOEXTDEPS, + (*C.struct_ly_ctx)(ypCtx))) { + if (Tracing == true) { + strData := yp.NodeDump((*YParserNode)(dataTmp)) + TRACE_LOG(INFO_API, TRACE_ONERROR, "Failed to validate Syntax, data = %v", strData) + } + return getErrorDetails() + } + //fmt.Printf("Error Code from libyang is %d\n", C.ly_errno) + + return YParserError {ErrCode : YP_SUCCESS,} +} + +//Perform semantic checks +func (yp *YParser) ValidateSemantics(data, depData, appDepData *YParserNode) YParserError { + + var dataTmp *C.struct_lyd_node + + if (data != nil) { + dataTmp = (*C.struct_lyd_node)(data) + } else if (depData != nil) { + dataTmp = (*C.struct_lyd_node)(depData) + } else if (yp.root != nil) { + dataTmp = (*C.struct_lyd_node)(yp.root) + } else { + if (yp.operation == "CREATE") || (yp.operation == "UPDATE") { + return YParserError {ErrCode : YP_INTERNAL_UNKNOWN,} + } else { + return YParserError {ErrCode : YP_SUCCESS,} + } + } + + //parse dependent data + if (data != nil && depData != nil) { + + //merge input data and dependent data for semantic validation + if (0 != C.lyd_merge_to_ctx(&dataTmp, (*C.struct_lyd_node)(depData), + C.LYD_OPT_DESTRUCT, (*C.struct_ly_ctx)(ypCtx))) { + TRACE_LOG(INFO_API, (TRACE_SEMANTIC | TRACE_LIBYANG), "Unable to merge dependent data\n") + return getErrorDetails() + } + } + + //Merge cached data + if ((data != nil || depData != nil) && yp.root != nil) { + if (0 != C.lyd_merge_to_ctx(&dataTmp, (*C.struct_lyd_node)(yp.root), + 0, (*C.struct_ly_ctx)(ypCtx))) { + TRACE_LOG(INFO_API, (TRACE_SEMANTIC | TRACE_LIBYANG), "Unable to merge cached dependent data\n") + return getErrorDetails() + } + } + + //Merge appDepData + if (appDepData != nil) { + if (0 != C.lyd_merge_to_ctx(&dataTmp, (*C.struct_lyd_node)(appDepData), + C.LYD_OPT_DESTRUCT, (*C.struct_ly_ctx)(ypCtx))) { + TRACE_LOG(INFO_API, (TRACE_SEMANTIC | TRACE_LIBYANG), "Unable to merge other dependent data\n") + return getErrorDetails() + } + } + + //Add operation for constraint check + if (ypOpRoot != nil) { + //if (0 != C.lyd_insert_sibling(&dataTmp, (*C.struct_lyd_node)(ypOpRoot))) { + if (0 != C.lyd_merge_to_ctx(&dataTmp, (*C.struct_lyd_node)(ypOpRoot), + 0, (*C.struct_ly_ctx)(ypCtx))) { + TRACE_LOG(INFO_API, (TRACE_SEMANTIC | TRACE_LIBYANG), "Unable to insert operation node") + return getErrorDetails() + } + } + + if (Tracing == true) { + strData := yp.NodeDump((*YParserNode)(dataTmp)) + TRACE_LOG(INFO_API, TRACE_YPARSER, "Semantics data = %v", strData) + } + + //Check semantic validation + if (0 != C.lyd_data_validate(&dataTmp, C.LYD_OPT_CONFIG, (*C.struct_ly_ctx)(ypCtx))) { + if (Tracing == true) { + strData1 := yp.NodeDump((*YParserNode)(dataTmp)) + TRACE_LOG(INFO_API, TRACE_ONERROR, "Failed to validate Semantics, data = %v", strData1) + } + return getErrorDetails() + } + + return YParserError {ErrCode : YP_SUCCESS,} +} + +func (yp *YParser) FreeNode(node *YParserNode) YParserError { + + C.lyd_free_withsiblings((*C.struct_lyd_node)(node)) + + return YParserError {ErrCode : YP_SUCCESS,} +} + +/* This function translates LIBYANG error code to valid YPARSER error code. */ +func translateLYErrToYParserErr(LYErrcode int) YParserRetCode { + var ypErrCode YParserRetCode + + switch LYErrcode { + case C.LYVE_SUCCESS: /**< no error */ + ypErrCode = YP_SUCCESS + case C.LYVE_XML_MISS, C.LYVE_INARG, C.LYVE_MISSELEM: /**< missing XML object */ + ypErrCode = YP_SYNTAX_MISSING_FIELD + case C.LYVE_XML_INVAL, C.LYVE_XML_INCHAR, C.LYVE_INMOD, C.LYVE_INELEM , C.LYVE_INVAL, C.LYVE_MCASEDATA:/**< invalid XML object */ + ypErrCode = YP_SYNTAX_INVALID_FIELD + case C.LYVE_EOF, C.LYVE_INSTMT, C.LYVE_INPAR, C.LYVE_INID, C.LYVE_MISSSTMT, C.LYVE_MISSARG: /**< invalid statement (schema) */ + ypErrCode = YP_SYNTAX_INVALID_INPUT_DATA + case C.LYVE_TOOMANY: /**< too many instances of some object */ + ypErrCode = YP_SYNTAX_MULTIPLE_INSTANCE + case C.LYVE_DUPID, C.LYVE_DUPLEAFLIST, C.LYVE_DUPLIST, C.LYVE_NOUNIQ:/**< duplicated identifier (schema) */ + ypErrCode = YP_SYNTAX_DUPLICATE + case C.LYVE_ENUM_INVAL: /**< invalid enum value (schema) */ + ypErrCode = YP_SYNTAX_ENUM_INVALID + case C.LYVE_ENUM_INNAME: /**< invalid enum name (schema) */ + ypErrCode = YP_SYNTAX_ENUM_INVALID_NAME + case C.LYVE_ENUM_WS: /**< enum name with leading/trailing whitespaces (schema) */ + ypErrCode = YP_SYNTAX_ENUM_WHITESPACE + case C.LYVE_KEY_NLEAF, C.LYVE_KEY_CONFIG, C.LYVE_KEY_TYPE : /**< list key is not a leaf (schema) */ + ypErrCode = YP_SEMANTIC_KEY_INVALID + case C.LYVE_KEY_MISS, C.LYVE_PATH_MISSKEY: /**< list key not found (schema) */ + ypErrCode = YP_SEMANTIC_KEY_NOT_EXIST + case C.LYVE_KEY_DUP: /**< duplicated key identifier (schema) */ + ypErrCode = YP_SEMANTIC_KEY_DUPLICATE + case C.LYVE_NOMIN:/**< min-elements constraint not honored (data) */ + ypErrCode = YP_SYNTAX_MINIMUM_INVALID + case C.LYVE_NOMAX:/**< max-elements constraint not honored (data) */ + ypErrCode = YP_SYNTAX_MAXIMUM_INVALID + case C.LYVE_NOMUST, C.LYVE_NOWHEN, C.LYVE_INWHEN, C.LYVE_NOLEAFREF : /**< unsatisfied must condition (data) */ + ypErrCode = YP_SEMANTIC_DEPENDENT_DATA_MISSING + case C.LYVE_NOMANDCHOICE:/**< max-elements constraint not honored (data) */ + ypErrCode = YP_SEMANTIC_MANDATORY_DATA_MISSING + case C.LYVE_PATH_EXISTS: /**< target node already exists (path) */ + ypErrCode = YP_SEMANTIC_KEY_ALREADY_EXIST + default: + ypErrCode = YP_INTERNAL_UNKNOWN + + } + return ypErrCode +} + +/* This function performs parsing and processing of LIBYANG error messages. */ +func getErrorDetails() YParserError { + var key []string + var errtableName string + var ElemVal string + var errMessage string + var ElemName string + var errText string + var msg string + var ypErrCode YParserRetCode = YP_INTERNAL_UNKNOWN + var errMsg, errPath, errAppTag string + + ctx := (*C.struct_ly_ctx)(ypCtx) + ypErrFirst := C.ly_err_first(ctx); + + + if (ypErrFirst == nil) { + return YParserError { + TableName : errtableName, + ErrCode : ypErrCode, + Keys : key, + Value : ElemVal, + Field : ElemName, + Msg : errMessage, + ErrTxt: errText, + ErrAppTag: errAppTag, + } + } + + + if ((ypErrFirst != nil) && ypErrFirst.prev.no == C.LY_SUCCESS) { + return YParserError { + ErrCode : YP_SUCCESS, + } + } + + if (ypErrFirst != nil) { + errMsg = C.GoString(ypErrFirst.prev.msg) + errPath = C.GoString(ypErrFirst.prev.path) + errAppTag = C.GoString(ypErrFirst.prev.apptag) + } + + + /* Example error messages. + 1. Leafref "/sonic-port:sonic-port/sonic-port:PORT/sonic-port:ifname" of value "Ethernet668" points to a non-existing leaf. + (path: /sonic-interface:sonic-interface/INTERFACE[portname='Ethernet668'][ip_prefix='10.0.0.0/31']/portname) + 2. A vlan interface member cannot be part of portchannel which is already a vlan member + (path: /sonic-vlan:sonic-vlan/VLAN[name='Vlan1001']/members[.='Ethernet8']) + 3. Value "ch1" does not satisfy the constraint "Ethernet([1-3][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[0-9])" (range, length, or pattern). + (path: /sonic-vlan:sonic-vlan/VLAN[name='Vlan1001']/members[.='ch1'])*/ + + + /* Fetch the TABLE Name which are in CAPS. */ + resultTable := strings.SplitN(errPath, "[", 2) + if (len(resultTable) >= 2) { + resultTab := strings.Split(resultTable[0], "/") + errtableName = resultTab[len(resultTab) -1] + + /* Fetch the Error Elem Name. */ + resultElem := strings.Split(resultTable[1], "/") + ElemName = resultElem[len(resultElem) -1] + } + + /* Fetch the invalid field name. */ + result := strings.Split(errMsg, "\"") + if (len(result) > 1) { + for i := range result { + if (strings.Contains(result[i], "value")) || + (strings.Contains(result[i], "Value")) { + ElemVal = result[i+1] + } + } + } else if (len(result) == 1) { + /* Custom contraint error message like in must statement. + This can be used by App to display to user. + */ + errText = errMsg + } + + // Find key elements + resultKey := strings.Split(errPath, "=") + for i := range resultKey { + if (strings.Contains(resultKey[i], "]")) { + newRes := strings.Split(resultKey[i], "]") + key = append(key, newRes[0]) + } + } + + /* Form the error message. */ + msg = "[" + for _, elem := range key { + msg = msg + elem + " " + } + msg = msg + "]" + + /* For non-constraint related errors , print below error message. */ + if (len(result) > 1) { + errMessage = errtableName + " with keys" + msg + " has field " + + ElemName + " with invalid value " + ElemVal + }else { + /* Dependent data validation error. */ + errMessage = "Dependent data validation failed for table " + + errtableName + " with keys" + msg + } + + + if (C.ly_errno == C.LY_EVALID) { //Validation failure + ypErrCode = translateLYErrToYParserErr(int(ypErrFirst.prev.vecode)) + + } else { + switch (C.ly_errno) { + case C.LY_EMEM: + errText = "Memory allocation failure" + + case C.LY_ESYS: + errText = "System call failure" + + case C.LY_EINVAL: + errText = "Invalid value" + + case C.LY_EINT: + errText = "Internal error" + + case C.LY_EPLUGIN: + errText = "Error reported by a plugin" + } + } + + errObj := YParserError { + TableName : errtableName, + ErrCode : ypErrCode, + Keys : key, + Value : ElemVal, + Field : ElemName, + Msg : errMessage, + ErrTxt: errText, + ErrAppTag: errAppTag, + } + + TRACE_LOG(INFO_API, TRACE_YPARSER, "YParser error details: %v...", errObj) + + return errObj +} + +func FindNode(root *YParserNode, xpath string) *YParserNode { + return (*YParserNode)(C.lyd_find_node((*C.struct_lyd_node)(root), C.CString(xpath))) +} diff --git a/cvl/jsondata_test.go b/cvl/jsondata_test.go new file mode 100644 index 000000000..87fe059e2 --- /dev/null +++ b/cvl/jsondata_test.go @@ -0,0 +1,70 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package cvl_test + +var json_edit_config_create_acl_table_dependent_data = []string{`{ + "stage": "INGRESS", + "type": "L3" + }`} + +var json_edit_config_create_acl_rule_config_data = []string{ + `{ + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000" + + + }`} + +var json_validate_config_data = []string{`{ + "INTERFACE": { + "Ethernet8|10.0.0.0/31": {}, + "Ethernet12|10.0.0.2/31": {}, + "Ethernet16|10.0.0.4/31": {} + } + }`, + `{ + "DEVICE_METADATA": { + "localhost": { + "hwsku": "Force10-S6100", + "default_bgp_status": "up", + "docker_routing_config_mode": "unified", + "hostname": "sonic-s6100-01", + "platform": "x86_64-dell_s6100_c2538-r0", + "mac": "4c:76:25:f4:70:82", + "default_pfcwd_status": "disable", + "deployment_id": "1", + "type": "ToRRouter" + } + } + }`, + `{ + "CABLE_LENGTH": { + "AZURE": { + "Ethernet8": "5m", + "Ethernet12": "5m", + "Ethernet16": "5m", + } + } + }`} diff --git a/cvl/schema/Makefile b/cvl/schema/Makefile new file mode 100644 index 000000000..e16280601 --- /dev/null +++ b/cvl/schema/Makefile @@ -0,0 +1,60 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +TOPDIR := ../.. +YANGDIR=$(TOPDIR)/models/yang +sonic_yang=$(YANGDIR)/sonic +std_yang_common=$(YANGDIR)/common/ +sonic_yang_common=$(sonic_yang)/common +pyang_plugin_dir=$(TOPDIR)/tools/pyang/pyang_plugins + +out_dir=$(TOPDIR)/build/cvl/schema + +src_files=$(wildcard $(sonic_yang)/*.yang) +src_files += $(wildcard $(sonic_yang_common)/*.yang) +out=$(patsubst %.yang, $(out_dir)/%.yin, $(notdir $(src_files))) +search_path=$(std_yang_common):$(sonic_yang):$(sonic_yang_common) + +all: schema + +schema: $(out) + +.PRECIOUS: %/. +%/.: + mkdir -p $(@D) + +#Build YANG models +$(out_dir)/%.yin:$(sonic_yang)/%.yang | $(out_dir)/. + @echo "Generating `basename $@` ..." + @devFile="`echo $@ | cut -d . -f1`-deviation.yang"; \ + if [ -f $$devFile ] ; then devOpt="--deviation-module $$devFile"; fi; \ + pyang -p $(search_path) --plugindir $(pyang_plugin_dir) \ + -f yin-cvl $$devOpt $< -o $@ + + +#Build common YANG models +$(out_dir)/%.yin:$(sonic_yang_common)/%.yang | $(out_dir)/. + @echo "Generating `basename $@` ..." + @devFile="`echo $@ | cut -d . -f1`-deviation.yang"; \ + if [ -f $$devFile ] ; then devOpt="--deviation-module $$devFile"; fi; \ + pyang -p $(search_path) --plugindir $(pyang_plugin_dir) \ + -f yin-cvl $$devOpt $< -o $@ + +clean: + $(RM) -r $(out_dir) diff --git a/cvl/testdata/acl_rule.json b/cvl/testdata/acl_rule.json new file mode 100644 index 000000000..46c5a3745 --- /dev/null +++ b/cvl/testdata/acl_rule.json @@ -0,0 +1,10 @@ +{ +"ACL_RULE": { + "TestACL13|Rule1": { + "PRIORITY": "55", + "PACKET_ACTION": "DROP", + "IP_TYPE" : "IPV4", + "L4_SRC_PORT": "0" + } + } +} diff --git a/cvl/testdata/aclrule.json b/cvl/testdata/aclrule.json new file mode 100644 index 000000000..41a768225 --- /dev/null +++ b/cvl/testdata/aclrule.json @@ -0,0 +1,9 @@ +{ + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "IP_TYPE" : "IPV4", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000" +} diff --git a/cvl/testdata/acltable.json b/cvl/testdata/acltable.json new file mode 100644 index 000000000..810caee0d --- /dev/null +++ b/cvl/testdata/acltable.json @@ -0,0 +1,4 @@ +{ +"stage": "INGRESS", +"type": "L3" +} diff --git a/cvl/testdata/config_db.json b/cvl/testdata/config_db.json new file mode 100644 index 000000000..ad2d952f2 --- /dev/null +++ b/cvl/testdata/config_db.json @@ -0,0 +1,107 @@ +{ + "VLAN": { + "Vlan100": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + }, + "Vlan1200": { + "members": [ + "Ethernet64", + "Ethernet8" + ], + "vlanid": "1200" + }, + "Vlan2500": { + "members": [ + "Ethernet8", + "Ethernet64" + ], + "vlanid": "2500" + } + }, + "VLAN_MEMBER": { + "Vlan100|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan100|Ethernet44": { + "tagging_mode": "tagged" + }, + "Vlan1200|Ethernet8": { + "tagging_mode": "tagged" + } + }, + "WRED_PROFILE": { + "AZURE_LOSSLESS": { + "red_max_threshold": "312000", + "wred_green_enable": "true", + "ecn": "ecn_all", + "green_min_threshold": "104000", + "red_min_threshold": "104000", + "wred_yellow_enable": "true", + "yellow_min_threshold": "104000", + "wred_red_enable": "true", + "yellow_max_threshold": "312000", + "green_max_threshold": "312000" + } + }, + "BUFFER_POOL": { + "egress_lossless_pool": { + "type": "egress", + "mode": "static", + "size": "12766208" + }, + "egress_lossy_pool": { + "type": "egress", + "mode": "dynamic", + "size": "8072396" + }, + "ingress_lossless_pool": { + "type": "ingress", + "mode": "dynamic", + "size": "12766208" + } + }, + "MIRROR_SESSION": { + "everflow0": { + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2" + } + }, + "SCHEDULER": { + "scheduler.0": { + "type": "DWRR", + "weight": "25" + }, + "scheduler.1": { + "type": "DWRR", + "weight": "30" + }, + "scheduler.2": { + "type": "DWRR", + "weight": "20" + } + }, + "QUEUE": { + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|0": { + "scheduler": "[SCHEDULER|scheduler.1]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|1": { + "scheduler": "[SCHEDULER|scheduler.2]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|3-4": { + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]", + "scheduler": "[SCHEDULER|scheduler.0]" + } + }, + "TC_TO_QUEUE_MAP": { + "AZURE": { + "1": "1", + "0": "0", + "3": "3", + "4": "4" + } + } +} diff --git a/cvl/testdata/config_db1.json b/cvl/testdata/config_db1.json new file mode 100644 index 000000000..ec6ce5e73 --- /dev/null +++ b/cvl/testdata/config_db1.json @@ -0,0 +1,133 @@ +{ + "PORT": { + "Ethernet0": { + "alias": "fortyGigE0/0", + "lanes": "29,30,31,32" + }, + "Ethernet4": { + "alias": "fortyGigE0/4", + "lanes": "25,26,27,28" + }, + "Ethernet8": { + "alias": "fortyGigE0/8", + "lanes": "37,38,39,40" + }, + "Ethernet12": { + "alias": "fortyGigE0/12", + "lanes": "33,34,35,36" + }, + "Ethernet16": { + "alias": "fortyGigE0/16", + "lanes": "41,42,43,44" + }, + "Ethernet20": { + "alias": "fortyGigE0/20", + "lanes": "45,46,47,48" + }, + "Ethernet24": { + "alias": "fortyGigE0/24", + "lanes": "5,6,7,8" + }, + "Ethernet28": { + "alias": "fortyGigE0/28", + "lanes": "1,2,3,4" + }, + "Ethernet32": { + "alias": "fortyGigE0/32", + "lanes": "9,10,11,12" + }, + "Ethernet36": { + "alias": "fortyGigE0/36", + "lanes": "13,14,15,16" + }, + "Ethernet40": { + "alias": "fortyGigE0/40", + "lanes": "21,22,23,24" + }, + "Ethernet44": { + "alias": "fortyGigE0/44", + "lanes": "17,18,19,20" + }, + "Ethernet48": { + "alias": "fortyGigE0/48", + "lanes": "49,50,51,52" + }, + "Ethernet52": { + "alias": "fortyGigE0/52", + "lanes": "53,54,55,56" + }, + "Ethernet56": { + "alias": "fortyGigE0/56", + "lanes": "61,62,63,64" + }, + "Ethernet60": { + "alias": "fortyGigE0/60", + "lanes": "57,58,59,60" + }, + "Ethernet64": { + "alias": "fortyGigE0/64", + "lanes": "65,66,67,68" + }, + "Ethernet68": { + "alias": "fortyGigE0/68", + "lanes": "69,70,71,72" + }, + "Ethernet72": { + "alias": "fortyGigE0/72", + "lanes": "77,78,79,80" + }, + "Ethernet76": { + "alias": "fortyGigE0/76", + "lanes": "73,74,75,76" + }, + "Ethernet80": { + "alias": "fortyGigE0/80", + "lanes": "105,106,107,108" + }, + "Ethernet84": { + "alias": "fortyGigE0/84", + "lanes": "109,110,111,112" + }, + "Ethernet88": { + "alias": "fortyGigE0/88", + "lanes": "117,118,119,120" + }, + "Ethernet92": { + "alias": "fortyGigE0/92", + "lanes": "113,114,115,116" + }, + "Ethernet96": { + "alias": "fortyGigE0/96", + "lanes": "121,122,123,124" + }, + "Ethernet100": { + "alias": "fortyGigE0/100", + "lanes": "125,126,127,128" + }, + "Ethernet104": { + "alias": "fortyGigE0/104", + "lanes": "85,86,87,88" + }, + "Ethernet108": { + "alias": "fortyGigE0/108", + "lanes": "81,82,83,84" + }, + "Ethernet112": { + "alias": "fortyGigE0/112", + "lanes": "89,90,91,92" + }, + "Ethernet116": { + "alias": "fortyGigE0/116", + "lanes": "93,94,95,96" + }, + "Ethernet120": { + "alias": "fortyGigE0/120", + "lanes": "97,98,99,100" + }, + "Ethernet124": { + "alias": "fortyGigE0/124", + "lanes": "101,102,103,104" + } + } +} + diff --git a/cvl/testdata/config_db2.json b/cvl/testdata/config_db2.json new file mode 100644 index 000000000..e6be83ac4 --- /dev/null +++ b/cvl/testdata/config_db2.json @@ -0,0 +1,3437 @@ +{ + "VLAN_MEMBER": { + "Vlan51|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan51|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan97|Ethernet4": { + "tagging_mode": "untagged" + }, + "Vlan99|Ethernet4": { + "tagging_mode": "untagged" + }, + "Vlan99|Ethernet108": { + "tagging_mode": "untagged" + }, + "Vlan99|Ethernet124": { + "tagging_mode": "untagged" + }, + "Vlan101|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan101|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan101|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan102|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan102|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan102|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan103|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan103|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan103|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan104|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan104|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan104|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan105|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan105|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan105|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan106|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan106|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan106|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan107|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan107|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan107|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan108|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan108|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan108|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan109|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan109|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan109|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan110|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan110|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan110|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan111|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan111|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan111|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan112|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan112|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan112|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan113|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan113|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan113|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan114|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan114|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan114|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan115|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan115|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan115|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan116|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan116|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan116|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan117|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan117|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan117|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan118|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan118|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan118|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan119|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan119|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan119|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan120|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan120|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan120|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan121|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan121|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan121|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan122|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan122|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan122|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan123|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan123|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan123|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan124|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan124|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan124|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan125|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan125|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan125|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan126|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan126|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan126|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan127|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan127|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan127|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan128|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan128|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan128|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan129|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan129|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan129|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan130|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan130|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan130|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan131|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan131|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan131|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan132|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan132|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan132|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan133|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan133|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan133|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan134|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan134|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan134|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan135|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan135|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan135|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan136|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan136|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan136|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan137|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan137|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan137|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan138|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan138|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan138|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan139|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan139|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan139|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan140|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan140|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan140|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan141|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan141|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan141|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan142|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan142|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan142|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan143|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan143|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan143|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan144|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan144|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan144|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan145|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan145|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan145|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan146|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan146|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan146|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan147|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan147|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan147|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan148|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan148|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan148|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan149|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan149|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan149|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan150|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan150|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan150|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan151|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan151|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan151|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan152|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan152|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan152|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan153|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan153|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan153|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan154|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan154|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan154|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan155|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan155|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan155|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan156|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan156|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan156|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan157|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan157|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan157|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan158|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan158|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan158|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan159|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan159|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan159|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan160|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan160|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan160|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan161|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan161|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan161|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan162|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan162|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan162|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan163|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan163|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan163|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan164|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan164|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan164|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan165|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan165|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan165|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan166|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan166|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan166|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan167|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan167|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan167|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan168|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan168|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan168|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan169|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan169|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan169|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan170|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan170|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan170|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan171|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan171|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan171|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan172|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan172|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan172|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan173|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan173|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan173|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan174|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan174|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan174|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan175|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan175|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan175|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan176|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan176|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan176|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan177|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan177|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan177|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan178|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan178|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan178|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan179|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan179|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan179|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan180|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan180|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan180|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan181|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan181|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan181|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan182|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan182|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan182|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan183|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan183|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan183|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan184|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan184|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan184|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan185|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan185|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan185|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan186|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan186|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan186|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan187|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan187|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan187|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan188|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan188|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan188|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan189|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan189|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan189|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan190|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan190|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan190|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan191|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan191|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan191|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan192|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan192|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan192|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan193|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan193|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan193|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan194|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan194|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan194|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan195|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan195|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan195|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan196|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan196|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan196|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan197|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan197|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan197|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan198|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan198|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan198|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan199|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan200|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan200|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan200|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan201|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan201|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan201|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan202|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan202|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan202|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan203|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan203|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan203|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan204|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan204|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan204|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan205|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan205|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan205|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan206|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan206|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan206|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan207|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan207|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan207|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan208|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan208|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan208|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan209|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan209|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan209|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan210|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan210|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan210|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan211|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan211|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan211|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan212|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan212|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan212|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan213|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan213|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan213|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan214|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan214|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan214|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan215|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan215|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan215|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan216|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan216|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan216|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan217|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan217|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan217|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan218|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan218|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan218|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan219|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan219|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan219|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan220|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan220|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan220|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan221|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan221|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan221|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan222|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan222|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan222|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan223|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan223|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan223|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan224|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan224|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan224|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan225|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan225|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan225|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan226|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan226|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan226|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan227|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan227|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan227|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan228|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan228|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan228|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan229|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan229|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan229|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan230|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan230|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan230|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan231|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan231|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan231|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan232|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan232|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan232|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan233|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan233|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan233|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan234|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan234|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan234|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan235|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan235|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan235|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan236|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan236|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan236|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan237|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan237|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan237|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan238|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan238|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan238|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan239|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan239|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan239|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan240|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan240|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan240|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan241|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan241|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan241|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan242|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan242|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan242|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan243|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan243|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan243|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan244|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan244|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan244|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan245|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan245|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan245|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan246|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan246|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan246|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan247|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan247|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan247|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan248|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan248|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan248|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan249|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan249|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan249|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan250|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan250|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan250|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan251|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan251|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan251|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan252|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan252|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan252|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan253|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan253|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan253|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan254|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan254|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan254|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan255|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan255|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan255|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan256|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan256|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan256|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan257|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan257|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan257|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan258|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan258|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan258|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan259|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan259|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan259|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan260|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan260|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan260|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan261|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan261|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan261|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan262|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan262|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan262|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan263|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan263|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan263|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan264|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan264|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan264|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan265|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan265|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan265|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan266|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan266|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan266|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan267|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan267|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan267|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan268|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan268|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan268|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan269|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan269|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan269|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan270|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan270|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan270|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan271|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan271|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan271|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan272|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan272|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan272|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan273|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan273|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan273|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan274|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan274|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan274|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan275|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan275|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan275|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan276|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan276|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan276|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan277|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan277|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan277|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan278|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan278|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan278|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan279|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan279|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan279|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan280|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan280|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan280|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan281|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan281|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan281|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan282|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan282|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan282|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan283|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan283|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan283|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan284|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan284|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan284|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan285|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan285|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan285|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan286|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan286|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan286|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan287|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan287|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan287|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan288|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan288|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan288|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan289|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan289|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan289|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan290|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan290|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan290|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan291|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan291|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan291|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan292|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan292|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan292|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan293|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan293|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan293|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan294|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan294|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan294|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan295|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan295|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan295|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan296|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan296|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan296|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan297|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan297|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan297|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan298|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan298|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan298|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan299|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan299|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan299|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan300|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan300|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan300|Ethernet4": { + "tagging_mode": "tagged" + } + }, + "VLAN": { + "Vlan51": { + "members": [ + "Ethernet108", + "Ethernet124" + ], + "vlanid": "51" + }, + "Vlan97": { + "members": [ + "Ethernet4" + ], + "vlanid": "97" + }, + "Vlan99": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "99" + }, + "Vlan101": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "101" + }, + "Vlan102": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "102" + }, + "Vlan103": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "103" + }, + "Vlan104": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "104" + }, + "Vlan105": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "105" + }, + "Vlan106": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "106" + }, + "Vlan107": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "107" + }, + "Vlan108": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "108" + }, + "Vlan109": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "109" + }, + "Vlan110": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "110" + }, + "Vlan111": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "111" + }, + "Vlan112": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "112" + }, + "Vlan113": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "113" + }, + "Vlan114": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "114" + }, + "Vlan115": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "115" + }, + "Vlan116": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "116" + }, + "Vlan117": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "117" + }, + "Vlan118": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "118" + }, + "Vlan119": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "119" + }, + "Vlan120": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "120" + }, + "Vlan121": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "121" + }, + "Vlan122": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "122" + }, + "Vlan123": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "123" + }, + "Vlan124": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "124" + }, + "Vlan125": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "125" + }, + "Vlan126": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "126" + }, + "Vlan127": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "127" + }, + "Vlan128": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "128" + }, + "Vlan129": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "129" + }, + "Vlan130": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "130" + }, + "Vlan131": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "131" + }, + "Vlan132": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "132" + }, + "Vlan133": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "133" + }, + "Vlan134": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "134" + }, + "Vlan135": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "135" + }, + "Vlan136": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "136" + }, + "Vlan137": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "137" + }, + "Vlan138": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "138" + }, + "Vlan139": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "139" + }, + "Vlan140": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "140" + }, + "Vlan141": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "141" + }, + "Vlan142": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "142" + }, + "Vlan143": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "143" + }, + "Vlan144": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "144" + }, + "Vlan145": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "145" + }, + "Vlan146": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "146" + }, + "Vlan147": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "147" + }, + "Vlan148": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "148" + }, + "Vlan149": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "149" + }, + "Vlan150": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "150" + }, + "Vlan151": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "151" + }, + "Vlan152": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "152" + }, + "Vlan153": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "153" + }, + "Vlan154": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "154" + }, + "Vlan155": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "155" + }, + "Vlan156": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "156" + }, + "Vlan157": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "157" + }, + "Vlan158": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "158" + }, + "Vlan159": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "159" + }, + "Vlan160": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "160" + }, + "Vlan161": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "161" + }, + "Vlan162": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "162" + }, + "Vlan163": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "163" + }, + "Vlan164": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "164" + }, + "Vlan165": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "165" + }, + "Vlan166": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "166" + }, + "Vlan167": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "167" + }, + "Vlan168": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "168" + }, + "Vlan169": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "169" + }, + "Vlan170": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "170" + }, + "Vlan171": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "171" + }, + "Vlan172": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "172" + }, + "Vlan173": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "173" + }, + "Vlan174": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "174" + }, + "Vlan175": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "175" + }, + "Vlan176": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "176" + }, + "Vlan177": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "177" + }, + "Vlan178": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "178" + }, + "Vlan179": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "179" + }, + "Vlan180": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "180" + }, + "Vlan181": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "181" + }, + "Vlan182": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "182" + }, + "Vlan183": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "183" + }, + "Vlan184": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "184" + }, + "Vlan185": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "185" + }, + "Vlan186": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "186" + }, + "Vlan187": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "187" + }, + "Vlan188": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "188" + }, + "Vlan189": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "189" + }, + "Vlan190": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "190" + }, + "Vlan191": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "191" + }, + "Vlan192": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "192" + }, + "Vlan193": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "193" + }, + "Vlan194": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "194" + }, + "Vlan195": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "195" + }, + "Vlan196": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "196" + }, + "Vlan197": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "197" + }, + "Vlan198": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "198" + }, + "Vlan199": { + "members": [ + "Ethernet64" + ], + "vlanid": "199" + }, + "Vlan200": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "200" + }, + "Vlan201": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "201" + }, + "Vlan202": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "202" + }, + "Vlan203": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "203" + }, + "Vlan204": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "204" + }, + "Vlan205": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "205" + }, + "Vlan206": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "206" + }, + "Vlan207": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "207" + }, + "Vlan208": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "208" + }, + "Vlan209": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "209" + }, + "Vlan210": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "210" + }, + "Vlan211": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "211" + }, + "Vlan212": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "212" + }, + "Vlan213": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "213" + }, + "Vlan214": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "214" + }, + "Vlan215": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "215" + }, + "Vlan216": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "216" + }, + "Vlan217": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "217" + }, + "Vlan218": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "218" + }, + "Vlan219": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "219" + }, + "Vlan220": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "220" + }, + "Vlan221": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "221" + }, + "Vlan222": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "222" + }, + "Vlan223": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "223" + }, + "Vlan224": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "224" + }, + "Vlan225": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "225" + }, + "Vlan226": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "226" + }, + "Vlan227": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "227" + }, + "Vlan228": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "228" + }, + "Vlan229": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "229" + }, + "Vlan230": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "230" + }, + "Vlan231": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "231" + }, + "Vlan232": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "232" + }, + "Vlan233": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "233" + }, + "Vlan234": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "234" + }, + "Vlan235": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "235" + }, + "Vlan236": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "236" + }, + "Vlan237": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "237" + }, + "Vlan238": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "238" + }, + "Vlan239": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "239" + }, + "Vlan240": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "240" + }, + "Vlan241": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "241" + }, + "Vlan242": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "242" + }, + "Vlan243": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "243" + }, + "Vlan244": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "244" + }, + "Vlan245": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "245" + }, + "Vlan246": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "246" + }, + "Vlan247": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "247" + }, + "Vlan248": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "248" + }, + "Vlan249": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "249" + }, + "Vlan250": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "250" + }, + "Vlan251": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "251" + }, + "Vlan252": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "252" + }, + "Vlan253": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "253" + }, + "Vlan254": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "254" + }, + "Vlan255": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "255" + }, + "Vlan256": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "256" + }, + "Vlan257": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "257" + }, + "Vlan258": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "258" + }, + "Vlan259": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "259" + }, + "Vlan260": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "260" + }, + "Vlan261": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "261" + }, + "Vlan262": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "262" + }, + "Vlan263": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "263" + }, + "Vlan264": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "264" + }, + "Vlan265": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "265" + }, + "Vlan266": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "266" + }, + "Vlan267": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "267" + }, + "Vlan268": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "268" + }, + "Vlan269": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "269" + }, + "Vlan270": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "270" + }, + "Vlan271": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "271" + }, + "Vlan272": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "272" + }, + "Vlan273": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "273" + }, + "Vlan274": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "274" + }, + "Vlan275": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "275" + }, + "Vlan276": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "276" + }, + "Vlan277": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "277" + }, + "Vlan278": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "278" + }, + "Vlan279": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "279" + }, + "Vlan280": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "280" + }, + "Vlan281": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "281" + }, + "Vlan282": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "282" + }, + "Vlan283": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "283" + }, + "Vlan284": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "284" + }, + "Vlan285": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "285" + }, + "Vlan286": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "286" + }, + "Vlan287": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "287" + }, + "Vlan288": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "288" + }, + "Vlan289": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "289" + }, + "Vlan290": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "290" + }, + "Vlan291": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "291" + }, + "Vlan292": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "292" + }, + "Vlan293": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "293" + }, + "Vlan294": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "294" + }, + "Vlan295": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "295" + }, + "Vlan296": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "296" + }, + "Vlan297": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "297" + }, + "Vlan298": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "298" + }, + "Vlan299": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "299" + }, + "Vlan300": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "300" + } + } +} diff --git a/cvl/testdata/create_acl_table.json b/cvl/testdata/create_acl_table.json new file mode 100644 index 000000000..604be2e2d --- /dev/null +++ b/cvl/testdata/create_acl_table.json @@ -0,0 +1,8 @@ +{ +"ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + } +} diff --git a/cvl/testdata/create_acl_table12.json b/cvl/testdata/create_acl_table12.json new file mode 100644 index 000000000..83f74d6f1 --- /dev/null +++ b/cvl/testdata/create_acl_table12.json @@ -0,0 +1,8 @@ +{ +"ACL_TABLE": { + "TestACL13": { + "stage": "INGRESS", + "type": "L3" + } + } +} diff --git a/cvl/testdata/create_acl_table13.json b/cvl/testdata/create_acl_table13.json new file mode 100644 index 000000000..83f74d6f1 --- /dev/null +++ b/cvl/testdata/create_acl_table13.json @@ -0,0 +1,8 @@ +{ +"ACL_TABLE": { + "TestACL13": { + "stage": "INGRESS", + "type": "L3" + } + } +} diff --git a/cvl/testdata/port_table.json b/cvl/testdata/port_table.json new file mode 100644 index 000000000..266731a6e --- /dev/null +++ b/cvl/testdata/port_table.json @@ -0,0 +1,165 @@ +{ + "PORT": { + "Ethernet0": { + "alias": "fortyGigE0/0", + "lanes": "29,30,31,32", + "index": "0" + }, + "Ethernet4": { + "alias": "fortyGigE0/4", + "lanes": "25,26,27,28", + "index": "1" + }, + "Ethernet8": { + "alias": "fortyGigE0/8", + "lanes": "37,38,39,40", + "index": "2" + }, + "Ethernet12": { + "alias": "fortyGigE0/12", + "lanes": "33,34,35,36", + "index": "3" + }, + "Ethernet16": { + "alias": "fortyGigE0/16", + "lanes": "41,42,43,44", + "index": "4" + }, + "Ethernet20": { + "alias": "fortyGigE0/20", + "lanes": "45,46,47,48", + "index": "5" + }, + "Ethernet24": { + "alias": "fortyGigE0/24", + "lanes": "5,6,7,8", + "index": "6" + }, + "Ethernet28": { + "alias": "fortyGigE0/28", + "lanes": "1,2,3,4", + "index": "7" + }, + "Ethernet32": { + "alias": "fortyGigE0/32", + "lanes": "9,10,11,12", + "index": "8" + }, + "Ethernet36": { + "alias": "fortyGigE0/36", + "lanes": "13,14,15,16", + "index": "9" + }, + "Ethernet40": { + "alias": "fortyGigE0/40", + "lanes": "21,22,23,24", + "index": "10" + }, + "Ethernet44": { + "alias": "fortyGigE0/44", + "lanes": "17,18,19,20", + "index": "11" + }, + "Ethernet48": { + "alias": "fortyGigE0/48", + "lanes": "49,50,51,52", + "index": "12" + }, + "Ethernet52": { + "alias": "fortyGigE0/52", + "lanes": "53,54,55,56", + "index": "13" + }, + "Ethernet56": { + "alias": "fortyGigE0/56", + "lanes": "61,62,63,64", + "index": "14" + }, + "Ethernet60": { + "alias": "fortyGigE0/60", + "lanes": "57,58,59,60", + "index": "15" + }, + "Ethernet64": { + "alias": "fortyGigE0/64", + "lanes": "65,66,67,68", + "index": "16" + }, + "Ethernet68": { + "alias": "fortyGigE0/68", + "lanes": "69,70,71,72", + "index": "17" + }, + "Ethernet72": { + "alias": "fortyGigE0/72", + "lanes": "77,78,79,80", + "index": "18" + }, + "Ethernet76": { + "alias": "fortyGigE0/76", + "lanes": "73,74,75,76", + "index": "19" + }, + "Ethernet80": { + "alias": "fortyGigE0/80", + "lanes": "105,106,107,108", + "index": "20" + }, + "Ethernet84": { + "alias": "fortyGigE0/84", + "lanes": "109,110,111,112", + "index": "21" + }, + "Ethernet88": { + "alias": "fortyGigE0/88", + "lanes": "117,118,119,120", + "index": "22" + }, + "Ethernet92": { + "alias": "fortyGigE0/92", + "lanes": "113,114,115,116", + "index": "23" + }, + "Ethernet96": { + "alias": "fortyGigE0/96", + "lanes": "121,122,123,124", + "index": "24" + }, + "Ethernet100": { + "alias": "fortyGigE0/100", + "lanes": "125,126,127,128", + "index": "25" + }, + "Ethernet104": { + "alias": "fortyGigE0/104", + "lanes": "85,86,87,88", + "index": "26" + }, + "Ethernet108": { + "alias": "fortyGigE0/108", + "lanes": "81,82,83,84", + "index": "27" + }, + "Ethernet112": { + "alias": "fortyGigE0/112", + "lanes": "89,90,91,92", + "index": "28" + }, + "Ethernet116": { + "alias": "fortyGigE0/116", + "lanes": "93,94,95,96", + "index": "29" + }, + "Ethernet120": { + "alias": "fortyGigE0/120", + "lanes": "97,98,99,100", + "index": "30" + }, + "Ethernet124": { + "alias": "fortyGigE0/124", + "lanes": "101,102,103,104", + "index": "31" + } + } +} + diff --git a/cvl/testdata/schema/Makefile b/cvl/testdata/schema/Makefile new file mode 100644 index 000000000..3c2a0ae7f --- /dev/null +++ b/cvl/testdata/schema/Makefile @@ -0,0 +1,64 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +TOPDIR := ../../.. +YANGDIR=$(TOPDIR)/models/yang +sonic_yang=$(YANGDIR)/sonic +std_yang_common=$(YANGDIR)/common/ +sonic_yang_common=$(sonic_yang)/common +pyang_plugin_dir=$(TOPDIR)/tools/pyang/pyang_plugins + +src_files=$(wildcard *.yang) +src_files+=$(wildcard $(sonic_yang_common)/*.yang) + +out_dir=$(TOPDIR)/build/tests/cvl/testdata/schema +out=$(patsubst %.yang, $(out_dir)/%.yin, $(notdir $(src_files)) ) +out_ext=$(patsubst %.yang, %.tree, $(src_files)) +search_path=$(std_yang_common):$(sonic_yang):$(sonic_yang_common) + +all:schema + +schema: $(out) + +schema-tree: $(out_ext) + +.PRECIOUS: %/. +%/.: + mkdir -p $(@D) + +$(out_dir)/%.yin:%.yang | $(out_dir)/. + @echo "Generating `basename $@` ..." + @devFile="`echo $< | cut -d . -f1`-dev.yang"; \ + if [ -f $$devFile ] ; then devOpt="--deviation-module $$devFile"; fi; \ + pyang -p $(search_path) \ + --plugindir $(pyang_plugin_dir) -f yin-cvl $$devOpt $< -o $@ + +$(out_dir)/%.yin: $(sonic_yang_common)/%.yang | $(out_dir)/. + pyang -p $(search_path) --plugindir $(pyang_plugin_dir) -f yin-cvl $< -o $@ + +%.tree:%.yang + @echo "Generating `basename $@` ..." + @devFile="`echo $< | cut -d . -f1`-dev.yang"; \ + if [ -f $$devFile ] ; then devOpt="--deviation-module $$devFile"; fi; \ + pyang -p $(search_path) \ + -f tree $$devOpt $< -o `basename $@` + +clean: + $(RM) -r $(out_dir) + $(RM) *.tree diff --git a/cvl/testdata/schema/sonic-acl-deviation.yang b/cvl/testdata/schema/sonic-acl-deviation.yang new file mode 100644 index 000000000..c1d701c29 --- /dev/null +++ b/cvl/testdata/schema/sonic-acl-deviation.yang @@ -0,0 +1,43 @@ +module sonic-acl-deviation { + namespace "http://github.com/Azure/sonic-acl-deviation"; + prefix acld; + yang-version 1.1; + + import sonic-acl { + prefix sacl; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC ACL Deviations"; + + revision 2019-05-15 { + description + "Initial revision."; + } +/* + deviation /sacl:sonic-acl/sacl:ACL_TABLE/sacl:type { + deviate replace { + type enumeration { + enum MIRROR; + enum L2; + enum L3; + } + } + } + + deviation /sacl:sonic-acl/sacl:ACL_RULE/sacl:PACKET_ACTION { + deviate replace { + type enumeration { + enum FORWARD; + enum DROP; + } + } + } + */ +} diff --git a/cvl/testdata/schema/sonic-bgp-neighbor.yang b/cvl/testdata/schema/sonic-bgp-neighbor.yang new file mode 100644 index 000000000..14504d6f9 --- /dev/null +++ b/cvl/testdata/schema/sonic-bgp-neighbor.yang @@ -0,0 +1,84 @@ +module sonic-bgp-neighbor { + namespace "http://github.com/Azure/sonic-bgp-neighbor"; + prefix sbn; + + import ietf-inet-types { + prefix inet; + } + + import sonic-common { + prefix scommon; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC BGP NEIGHBOR"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-bgp-neighbor { + + container BGP_NEIGHBOR { + + list BGP_NEIGHBOR_LIST { + key "ipaddress"; + + leaf ipaddress{ + type inet:ip-address; + } + + leaf rrclient { + type uint8 { + range "0..255"; + } + } + + leaf admin_status{ + type scommon:admin-status; + } + + leaf peer_addr{ + type inet:ip-address; + } + + leaf name { + type string; + } + + leaf local_addr { + type inet:ipv4-address; + } + + leaf nhopself { + type uint8 { + range "0..255"; + } + } + + leaf holdtime { + type uint8 { + range "0..255"; + } + } + + leaf asn { + type uint64; + } + + leaf keepalive { + type uint8 { + range "0..255"; + } + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-buffer-pg.yang b/cvl/testdata/schema/sonic-buffer-pg.yang new file mode 100644 index 000000000..27918081e --- /dev/null +++ b/cvl/testdata/schema/sonic-buffer-pg.yang @@ -0,0 +1,66 @@ +module sonic-buffer-pg { + namespace "http://github.com/Azure/sonic-buffer-pg"; + prefix bpg; + + import sonic-extension { + prefix sonic-ext; + } + + import sonic-port { + prefix prt; + } + + import sonic-buffer-profile { + prefix bpf; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC BUFFER PG"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + + container sonic-buffer-pg { + + container BUFFER_PG { + + list BUFFER_PG_LIST { + key "ifname pg_num"; + sonic-ext:key-pattern "BUFFER_PG|({ifname},)*|{pg_num}"; //special pattern used for extracting keys from + //redis-key and fill populate the yang instance + // Total list instance = number(key1) * number(key2) * number(key3) + + leaf ifname { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + leaf pg_num { + type string { + pattern "[0-7]((-)[0-7])?" { + error-message "Invalid Buffer PG number"; + error-app-tag pg-num-invalid; + } + } + } + + leaf profile { //Hash reference key + type leafref { + path "/bpf:sonic-buffer-profile/bpf:BUFFER_PROFILE/bpf:BUFFER_PROFILE_LIST/bpf:name"; + } + } + + } + } + } +} diff --git a/cvl/testdata/schema/sonic-buffer-pool.yang b/cvl/testdata/schema/sonic-buffer-pool.yang new file mode 100644 index 000000000..5f935b3f9 --- /dev/null +++ b/cvl/testdata/schema/sonic-buffer-pool.yang @@ -0,0 +1,51 @@ +module sonic-buffer-pool { + namespace "http://github.com/Azure/sonic-buffer-pool"; + prefix bpl; + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC BUFFER POOL"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-buffer-pool { + + container BUFFER_POOL { + + list BUFFER_POOL_LIST { + key "name"; + + leaf name { + type string; + } + + leaf type { + type enumeration { + enum ingress; + enum egress; + } + } + + leaf mode { + type enumeration { + enum static; + enum dynamic; + } + } + + leaf size { + type uint64; + } + + } + } + } +} diff --git a/cvl/testdata/schema/sonic-buffer-profile.yang b/cvl/testdata/schema/sonic-buffer-profile.yang new file mode 100644 index 000000000..71f077654 --- /dev/null +++ b/cvl/testdata/schema/sonic-buffer-profile.yang @@ -0,0 +1,67 @@ +module sonic-buffer-profile { + namespace "http://github.com/Azure/sonic-buffer-profile"; + prefix bpf; + + import sonic-buffer-pool { + prefix bpl; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC BUFFER PROFILE"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + + container sonic-buffer-profile { + + container BUFFER_PROFILE { + + list BUFFER_PROFILE_LIST { + key "name"; + + leaf name { + type string; + } + + leaf static_th { + type uint64; + } + + leaf dynamic_th { + type int64; + } + + leaf size { + type uint64; + } + + leaf pool { + type leafref { + path "/bpl:sonic-buffer-pool/bpl:BUFFER_POOL/bpl:BUFFER_POOL_LIST/bpl:name"; + } + } + + leaf xon_offset { + type uint64; + } + + leaf xon { + type uint64; + } + + leaf xoff { + type uint64; + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-cablelength.yang b/cvl/testdata/schema/sonic-cablelength.yang new file mode 100644 index 000000000..af4746211 --- /dev/null +++ b/cvl/testdata/schema/sonic-cablelength.yang @@ -0,0 +1,60 @@ +module sonic-cablelength { + namespace "http://github.com/Azure/sonic-cablelength"; + prefix scl; + + import sonic-extension { + prefix sonic-ext; + } + + import sonic-port { + prefix prt; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC CABLELENGTH"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-cablelength { + + container CABLE_LENGTH { + + list CABLE_LENGTH_LIST { + key "name"; + sonic-ext:map-list true; //special conversion for map tables + sonic-ext:map-leaf "port length"; //every key:value pair is mapped to list keys, e.g. "1":"7" ==> tc_num=1, dscp=7 + + leaf name { + type string; + } + + list CABLE_LENGTH { //this is list inside list for storing mapping between two fields + key "port length"; + + leaf port { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + + } + + leaf length { + type string { + pattern "[0-9]?[0-9]m"; + } + } + } + + } + } + } +} diff --git a/cvl/testdata/schema/sonic-device-metadata.yang b/cvl/testdata/schema/sonic-device-metadata.yang new file mode 100644 index 000000000..014f88cd4 --- /dev/null +++ b/cvl/testdata/schema/sonic-device-metadata.yang @@ -0,0 +1,97 @@ +module sonic-device-metadata { + namespace "http://github.com/Azure/sonic-device-metadata"; + prefix sdm; + + import ietf-yang-types { + prefix yang; + } + + import sonic-common { + prefix scommon; + } + + import sonic-bgp-neighbor { + prefix sbn; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC DEVICE METADATA"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-device-metadata { + + container DEVICE_METADATA { + + list DEVICE_METADATA_LIST { + key "name"; + + leaf name{ + type string; + } + + leaf hwsku { + type string; + } + + leaf hostname { + type string; + } + + leaf platform { + type string; + } + + leaf mac { + type yang:mac-address; + } + + leaf bgp_asn { + type leafref { + path "/sbn:sonic-bgp-neighbor/sbn:BGP_NEIGHBOR/sbn:BGP_NEIGHBOR_LIST/sbn:asn"; + } + } + + leaf default_pfcwd_status { + type enumeration { + enum enable; + enum disable; + } + } + + leaf default_bgp_status { + type scommon:admin-status; + } + + leaf docker_routing_config_mode { + type enumeration { + enum unified; + enum separated; + } + } + + leaf deployment_id { + type uint8 { + range "0..255"; + } + } + + leaf type { + type enumeration { + enum ToRRouter; + enum LeafRouter; + } + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-device-neighbor.yang b/cvl/testdata/schema/sonic-device-neighbor.yang new file mode 100644 index 000000000..a91302ac2 --- /dev/null +++ b/cvl/testdata/schema/sonic-device-neighbor.yang @@ -0,0 +1,72 @@ +module sonic-device-neighbor { + namespace "http://github.com/Azure/sonic-device-neighbor"; + prefix sdn; + + import ietf-inet-types { + prefix inet; + } + + import sonic-port { + prefix prt; + } + + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC DEVICE NEIGHBOR"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-device-neighbor { + + container DEVICE_NEIGHBOR { + + list DEVICE_NEIGHBOR_LIST { + key "name"; + + leaf name{ + type string; + } + + leaf mgmt_addr{ + type inet:ip-address; + } + + leaf hwsku { + type string; + } + + leaf lo_addr { + type inet:ip-address; + } + + leaf local_port { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + leaf type { + type enumeration { + enum ToRRouter; + enum LeafRouter; + } + } + + leaf port { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-dscp-tc-map.yang b/cvl/testdata/schema/sonic-dscp-tc-map.yang new file mode 100644 index 000000000..14dccddd3 --- /dev/null +++ b/cvl/testdata/schema/sonic-dscp-tc-map.yang @@ -0,0 +1,58 @@ +module sonic-dscp-tc-map { + namespace "http://github.com/Azure/sonic-dscp-tc-map"; + prefix dtm; + + import sonic-extension { + prefix sonic-ext; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC DSCP_TO_TC_MAP"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-dscp-tc-map { + + container DSCP_TO_TC_MAP { + + list DSCP_TO_TC_MAP_LIST { + key "name"; + sonic-ext:map-list true; //special conversion for map tables + sonic-ext:map-leaf "dscp tc_num"; //every key:value pair is mapped to list keys, e.g. "1":"7" ==> tc_num=1, dscp=7 + + leaf name { + type string; + } + + list DSCP_TO_TC_MAP { //this is list inside list for storing mapping between two fields + key "dscp tc_num"; + + leaf tc_num { + type string { + pattern "[0-9]?"{ + error-message "Invalid Traffic Class number"; + error-app-tag tc-num-invalid; + } + } + } + + leaf dscp { + type string { + pattern "[1-9][0-9]?|[0-9]?"; + } + } + } + + } + } + } +} diff --git a/cvl/testdata/schema/sonic-mirror-session.yang b/cvl/testdata/schema/sonic-mirror-session.yang new file mode 100644 index 000000000..f9e22691d --- /dev/null +++ b/cvl/testdata/schema/sonic-mirror-session.yang @@ -0,0 +1,60 @@ +module sonic-mirror-session { + namespace "http://github.com/Azure/sonic-mirror-session"; + prefix sms; + + import ietf-inet-types { + prefix inet; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONiC MIRROR SESSION"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-mirror-session { + + container MIRROR_SESSION { + + list MIRROR_SESSION_LIST { + key "name"; + + leaf name { + type string; + } + + leaf src_ip { + type inet:ipv4-address; + } + + leaf dst_ip { + type inet:ipv4-address; + } + + leaf gre_type { + type string; + } + + leaf dscp { + type uint8; + } + + leaf ttl { + type uint8; + } + + leaf queue { + type uint8; + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-pf-limits.yang b/cvl/testdata/schema/sonic-pf-limits.yang new file mode 100644 index 000000000..658a6e9a8 --- /dev/null +++ b/cvl/testdata/schema/sonic-pf-limits.yang @@ -0,0 +1,44 @@ +module sonic-pf-limits { + namespace "http://github.com/Azure/sonic-pf-limits"; + prefix spf; + yang-version 1.1; + + import sonic-extension { + prefix sonic-ext; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC Platform constrainst"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-pf-limits { + sonic-ext:db-name "APPL_DB"; + + container acl { + leaf MAX_ACL_RULES { + type uint16; + } + leaf MAX_PRIORITY { + type uint16 { + range "1..65535"; + } + } + + } + container vlan { + leaf MAX_VLANS { + type uint16; + } + } + } +} diff --git a/cvl/testdata/schema/sonic-pfc-priority-queue-map.yang b/cvl/testdata/schema/sonic-pfc-priority-queue-map.yang new file mode 100644 index 000000000..ad51874d6 --- /dev/null +++ b/cvl/testdata/schema/sonic-pfc-priority-queue-map.yang @@ -0,0 +1,55 @@ +module sonic-pfc-priority-queue-map { + namespace "http://github.com/Azure/sonic-pfc-priority-queue-map"; + prefix ppq; + + import sonic-extension { + prefix sonic-ext; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC MAP_PFC_PRIORITY_TO_QUEUE"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-pfc-priority-queue-map { + + container MAP_PFC_PRIORITY_TO_QUEUE { + + list MAP_PFC_PRIORITY_TO_QUEUE_LIST { + key "name"; + sonic-ext:map-list true; //special conversion for map tables + sonic-ext:map-leaf "pfc_priority qindex"; //every key:value pair is mapped to list keys, e.g. "1":"7" ==> tc_num=1, dscp=7 + + leaf name { + type string; + } + + list MAP_PFC_PRIORITY_TO_QUEUE { //this is list inside list for storing mapping between two fields + key "pfc_priority qindex"; + + leaf pfc_priority { + type string { + pattern "[0-9]?"; + } + } + + leaf qindex { + type string { + pattern "[0-9]?"; + } + } + } + + } + } + } +} diff --git a/cvl/testdata/schema/sonic-port-qos-map.yang b/cvl/testdata/schema/sonic-port-qos-map.yang new file mode 100644 index 000000000..f9785cb46 --- /dev/null +++ b/cvl/testdata/schema/sonic-port-qos-map.yang @@ -0,0 +1,87 @@ +module sonic-port-qos-map { + namespace "http://github.com/Azure/sonic-port-qos-map"; + prefix pqm; + + import sonic-extension { + prefix sonic-ext; + } + + import sonic-port { + prefix prt; + } + + import sonic-tc-priority-group-map { + prefix tpg; + } + + import sonic-tc-queue-map { + prefix tqm; + } + + import sonic-pfc-priority-queue-map { + prefix ppq; + } + + import sonic-dscp-tc-map { + prefix dtm; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC PORT_QOS_MAP"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-port-qos-map { + + list PORT_QOS_MAP { + key "ifname"; + sonic-ext:key-pattern "PORT_QOS_MAP|({ifname},)*"; //special pattern used for extracting keys from redis-key and fill populate the yang instance + // Total list instance = number(key1) * number(key2) * number(key3) + + leaf ifname { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + leaf tc_to_pg_map { + type leafref { + path "/tpg:sonic-tc-priority-group-map/tpg:TC_TO_PRIORITY_GROUP_MAP/tpg:TC_TO_PRIORITY_GROUP_MAP_LIST/tpg:name"; + } + } + + leaf tc_to_queue_map { + type leafref { + path "/tqm:sonic-tc-queue-map/tqm:TC_TO_QUEUE_MAP/tqm:TC_TO_QUEUE_MAP_LIST/tqm:name"; + } + } + + leaf pfc-enable { + type string { + pattern "[0-9](,[0-9])?"; + } + } + + leaf pfc_to_queue_map { + type leafref { + path "/ppq:sonic-pfc-priority-queue-map/ppq:MAP_PFC_PRIORITY_TO_QUEUE/ppq:MAP_PFC_PRIORITY_TO_QUEUE_LIST/ppq:name"; + } + } + + leaf dscp_to_tc_map { + type leafref { + path "/dtm:sonic-dscp-tc-map/dtm:DSCP_TO_TC_MAP/dtm:DSCP_TO_TC_MAP_LIST/dtm:name"; + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-portchannel-interface.yang b/cvl/testdata/schema/sonic-portchannel-interface.yang new file mode 100644 index 000000000..45d92787d --- /dev/null +++ b/cvl/testdata/schema/sonic-portchannel-interface.yang @@ -0,0 +1,48 @@ +module sonic-portchannel-interface { + namespace "http://github.com/Azure/sonic-portchannel-interface"; + prefix spchint; + + import ietf-inet-types { + prefix inet; + } + + import sonic-portchannel { + prefix spc; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC PORTCHANNEL INTERFACE"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-portchannel-interface { + + container PORTCHANNEL_INTERFACE { + + list PORTCHANNEL_INTERFACE_LIST { + key "pch_name ip_prefix"; + + leaf pch_name{ + type leafref { + path "/spc:sonic-portchannel/spc:PORTCHANNEL/spc:PORTCHANNEL_LIST/spc:name"; + } + } + + leaf ip_prefix { + mandatory true; + type inet:ip-prefix; + + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-portchannel.yang b/cvl/testdata/schema/sonic-portchannel.yang new file mode 100644 index 000000000..afe308f4e --- /dev/null +++ b/cvl/testdata/schema/sonic-portchannel.yang @@ -0,0 +1,77 @@ +module sonic-portchannel { + namespace "http://github.com/Azure/sonic-portchannel"; + prefix spc; + + import sonic-common { + prefix scommon; + } + + import sonic-port { + prefix prt; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC PORTCHANNEL"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-portchannel { + + container PORTCHANNEL { + + list PORTCHANNEL_LIST { + key "name"; + + max-elements 3; + + leaf name { + type string; + } + + leaf admin_status { + type scommon:admin-status; + } + + leaf mtu { + type uint16; + } + + leaf min_links { + type uint8; + } + + leaf fallback { + type boolean; + } + } + } + + container PORTCHANNEL_MEMBER { + + list PORTCHANNEL_MEMBER_LIST { + key "name ifname"; + + leaf name { + type leafref { + path "../../../PORTCHANNEL/PORTCHANNEL_LIST/name"; + } + } + + leaf ifname { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-queue.yang b/cvl/testdata/schema/sonic-queue.yang new file mode 100644 index 000000000..92a7f4c96 --- /dev/null +++ b/cvl/testdata/schema/sonic-queue.yang @@ -0,0 +1,74 @@ +module sonic-queue { + namespace "http://github.com/Azure/sonic-queue"; + prefix squeue; + + import sonic-extension { + prefix sonic-ext; + } + + import sonic-port { + prefix prt; + } + + import sonic-scheduler { + prefix sch; + } + + import sonic-wred-profile { + prefix wrd; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC QUEUE"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + + container sonic-queue { + + container QUEUE { + + list QUEUE_LIST { + key "ifname qindex"; + sonic-ext:key-pattern "QUEUE|({ifname},)*|{qindex}"; //special pattern used for extracting keys from redis-key and populate the yang instance + // Total list instances = number(key1) * number(key2) * number(key3) + + leaf ifname { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + leaf qindex { + type string { + pattern "[0-8]((-)[0-8])?"{ + error-message "Invalid Q-index"; + error-app-tag qindex-invalid; + } + } + } + + leaf scheduler { + type leafref { + path "/sch:sonic-scheduler/sch:SCHEDULER/sch:SCHEDULER_LIST/sch:name"; + } + } + + leaf wred_profile { + type leafref { + path "/wrd:sonic-wred-profile/wrd:WRED_PROFILE/wrd:WRED_PROFILE_LIST/wrd:name"; + } + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-scheduler.yang b/cvl/testdata/schema/sonic-scheduler.yang new file mode 100644 index 000000000..2fc7997f7 --- /dev/null +++ b/cvl/testdata/schema/sonic-scheduler.yang @@ -0,0 +1,52 @@ +module sonic-scheduler { + namespace "http://github.com/Azure/sonic-scheduler"; + prefix sch; + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC SCHEDULER"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-scheduler { + + container SCHEDULER { + + list SCHEDULER_LIST { + key "name"; + + leaf name{ + type string; + } + + leaf type { + type enumeration { + enum DWRR; + enum WRR; + enum PRIORITY; + } + } + + leaf weight { + type uint8 { + range "0..255"; + } + } + + leaf priority { + type uint8 { + range "0..9"; + } + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-tc-priority-group-map.yang b/cvl/testdata/schema/sonic-tc-priority-group-map.yang new file mode 100644 index 000000000..ed89a281e --- /dev/null +++ b/cvl/testdata/schema/sonic-tc-priority-group-map.yang @@ -0,0 +1,54 @@ +module sonic-tc-priority-group-map { + namespace "http://github.com/Azure/sonic-tc-priority-group-map"; + prefix tpg; + + import sonic-extension { + prefix sonic-ext; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC TC_TO_PRIORITY_GROUP_MAP"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-tc-priority-group-map { + + container TC_TO_PRIORITY_GROUP_MAP { + + list TC_TO_PRIORITY_GROUP_MAP_LIST { + key "name"; + sonic-ext:map-list "true"; //special conversion for map tables + sonic-ext:map-leaf "tc_num pg_num"; //every key:value pair is mapped to list keys, e.g. "1":"7" ==> tc_num=1, dscp=7 + + leaf name { + type string; + } + + list TC_TO_PRIORITY_GROUP_MAP { //this is list inside list for storing mapping between two fields + key "tc_num pg_num"; + + leaf tc_num { + type string { + pattern "[0-9]?"; + } + } + + leaf pg_num { + type string { + pattern "[0-7]?"; + } + } + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-tc-queue-map.yang b/cvl/testdata/schema/sonic-tc-queue-map.yang new file mode 100644 index 000000000..37ab1c811 --- /dev/null +++ b/cvl/testdata/schema/sonic-tc-queue-map.yang @@ -0,0 +1,55 @@ +module sonic-tc-queue-map { + namespace "http://github.com/Azure/sonic-tc-queue-map"; + prefix tqm; + + import sonic-extension { + prefix sonic-ext; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC TC_TO_QUEUE_MAP"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-tc-queue-map { + + container TC_TO_QUEUE_MAP { + + list TC_TO_QUEUE_MAP_LIST { + key "name"; + sonic-ext:map-list "true"; //special conversion for map tables + sonic-ext:map-leaf "tc_num qindex"; //every key:value pair is mapped to list keys, e.g. "1":"7" ==> tc_num=1, qindex=7 + + leaf name { + type string; + } + + list TC_TO_QUEUE_MAP { //this is list inside list for storing mapping between two fields + key "tc_num qindex"; + + leaf tc_num { + type string { + pattern "[0-9]?"; + } + } + + leaf qindex { + type string { + pattern "[0-9]?"; + } + } + } + + } + } + } +} diff --git a/cvl/testdata/schema/sonic-vlan-deviation.yang b/cvl/testdata/schema/sonic-vlan-deviation.yang new file mode 100644 index 000000000..ff426fb30 --- /dev/null +++ b/cvl/testdata/schema/sonic-vlan-deviation.yang @@ -0,0 +1,36 @@ +module sonic-vlan-deviation { + namespace "http://github.com/Azure/sonic-vlan-deviation"; + prefix svd; + yang-version 1.1; + + /* + import sonic-vlan { + prefix svlan; + } + */ + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC VLAN deviation file"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + /* + deviation /svlan:sonic-vlan/svlan:VLAN/svlan:name { + deviate replace { + type string { + // Supports 3K VLANs + pattern "Vlan([1-3][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[1-9])"; + } + } + } + */ +} diff --git a/cvl/testdata/schema/sonic-vlan-interface.yang b/cvl/testdata/schema/sonic-vlan-interface.yang new file mode 100644 index 000000000..554feb1f5 --- /dev/null +++ b/cvl/testdata/schema/sonic-vlan-interface.yang @@ -0,0 +1,48 @@ +module sonic-vlan-interface { + namespace "http://github.com/Azure/sonic-vlan-interface"; + prefix svint; + + import ietf-inet-types { + prefix inet; + } + + import sonic-vlan { + prefix svlan; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC VLAN INTERFACE"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-vlan-interface { + + container VLAN_INTERFACE { + + list VLAN_INTERFACE_LIST { + key "portname ip_prefix"; + + leaf portname{ + type leafref { + path "/svlan:sonic-vlan/svlan:VLAN/svlan:VLAN_LIST/svlan:name"; + } + } + + leaf ip_prefix { + mandatory true; + type inet:ip-prefix; + + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-vlan.yang b/cvl/testdata/schema/sonic-vlan.yang new file mode 100644 index 000000000..1170960df --- /dev/null +++ b/cvl/testdata/schema/sonic-vlan.yang @@ -0,0 +1,107 @@ +module sonic-vlan { + namespace "http://github.com/Azure/sonic-vlan"; + prefix svlan; + yang-version 1.1; + + import sonic-common { + prefix scommon; + } + + import sonic-port { + prefix prt; + } + + import sonic-portchannel { + prefix spc; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC VLAN"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + + container sonic-vlan { + + container VLAN { + + list VLAN_LIST { + key "name"; + must "./name = concat('Vlan', string(./vlanid))"{ + error-app-tag vlan-invalid; + } + + leaf name { + type string { + pattern "Vlan(409[0-5]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[1-9])" { + error-message "Invalid Vlan name pattern"; + error-app-tag vlan-name-invalid; + } + } + } + + leaf vlanid { + mandatory true; + type uint16 { + range "1..4095" { + error-message "Vlan ID out of range"; + error-app-tag vlanid-invalid; + } + } + } + + leaf-list members { + must "count(../members[text()=/spc:sonic-portchannel/spc:PORTCHANNEL_MEMBER/" + + "spc:PORTCHANNEL_MEMBER_LIST[spc:ifname=current()]/spc:name]) = 0 and " + + "count(../members[text()=/spc:sonic-portchannel/spc:PORTCHANNEL_MEMBER/" + + "spc:PORTCHANNEL_MEMBER_LIST[spc:name=current()]/spc:ifname]) = 0 " { + error-message "A vlan interface member cannot be part of portchannel which is already a vlan member"; + } + + + type union { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + type leafref { + path "/spc:sonic-portchannel/spc:PORTCHANNEL/spc:PORTCHANNEL_LIST/spc:name"; + } + } + } + } + } + + container VLAN_MEMBER { + + list VLAN_MEMBER_LIST { + key "name ifname"; + + leaf name { + type leafref { + path "../../../VLAN/VLAN_LIST/name"; + } + } + + leaf ifname { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + leaf tagging_mode { + type scommon:tagging_mode; + default tagged; + } + } + } + } +} diff --git a/cvl/testdata/schema/sonic-wred-profile.yang b/cvl/testdata/schema/sonic-wred-profile.yang new file mode 100644 index 000000000..e3c5abe36 --- /dev/null +++ b/cvl/testdata/schema/sonic-wred-profile.yang @@ -0,0 +1,80 @@ +module sonic-wred-profile { + namespace "http://github.com/Azure/sonic-wred-profile"; + prefix wrd; + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC WRED_PROFILE"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-wred-profile { + + container WRED_PROFILE { + + list WRED_PROFILE_LIST { + key "name"; + + leaf name{ + type string; + } + + leaf yellow_min_threshold { + type uint64; + } + + leaf green_min_threshold { + type uint64; + } + + leaf red_min_threshold { + type uint64; + } + leaf yellow_max_threshold { + type uint64; + } + + leaf green_max_threshold { + type uint64; + } + + leaf red_max_threshold { + type uint64; + } + + leaf ecn { + type enumeration { + enum ecn_none; + enum ecn_green; + enum ecn_yellow; + enum ecn_red; + enum ecn_green_yellow; + enum ecn_green_red; + enum ecn_yellow_red; + enum ecn_all; + } + } + + leaf wred_green_enable { + type boolean; + } + + leaf wred_yellow_enable { + type boolean; + } + + leaf wred_red_enable { + type boolean; + } + } + } + } +} diff --git a/cvl/tests/Makefile b/cvl/tests/Makefile new file mode 100644 index 000000000..4e1fd530d --- /dev/null +++ b/cvl/tests/Makefile @@ -0,0 +1,36 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +SRC_FILES=$(wildcard *.go) +OUT=$(patsubst %.go, %, $(SRC_FILES)) +TOPDIR := ../.. +GO ?= go + +all:tests + +tests: $(OUT) + +%:%.go + make -C ../testdata/schema + @echo "Building $@ ..." + $(GO) build -mod=vendor -gcflags="all=-N -l" $< + +clean: + @echo "Removing files ..." + rm -rf $(OUT) diff --git a/cvl/tests/acl_rule.json b/cvl/tests/acl_rule.json new file mode 100644 index 000000000..c26cdee0e --- /dev/null +++ b/cvl/tests/acl_rule.json @@ -0,0 +1,22 @@ +{ +"ACL_TABLE": { + "TestACL11": { + "type": "L3", + "ports": "Ethernet0" + } + }, +"ACL_RULE": { + "TestACL11|Rule1": { + "PRIORITY": "55", + "PACKET_ACTION": "DROP", + "IP_TYPE" : "ANY", + "L4_SRC_PORT": "0" + }, + "TestACL11|Rule2": { + "PRIORITY": "55", + "PACKET_ACTION": "DROP", + "IP_TYPE" : "ANY", + "L4_SRC_PORT": "1" + } + } +} diff --git a/cvl/tests/cfg_validator.go b/cvl/tests/cfg_validator.go new file mode 100644 index 000000000..ab102439c --- /dev/null +++ b/cvl/tests/cfg_validator.go @@ -0,0 +1,273 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package main + +import ( + "fmt" + "os" + "time" + "io/ioutil" + "github.com/Azure/sonic-mgmt-common/cvl" +) + +func main() { + jsonData :=`{ + "VLAN": { + "Vlan100": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + }, + "Vlan1200": { + "members": [ + "Ethernet64", + "Ethernet8" + ], + "vlanid": "1200" + }, + "Vlan2500": { + "members": [ + "Ethernet8", + "Ethernet64" + ], + "vlanid": "2500" + } + } + }` + /* + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "l3" + } + }, + "ACL_RULE": { + "TestACL1|Rule1": { + "packet_action": "forward", + "src_ip": "10.1.1.1/32", + "l4_src_port": "ABC", + "ip_protocol": "ip", + "dst_ip": "20.2.2.2/32", + "l4_dst_port_range": "9000-12000", + "mirror_action" : "mirror1" + } + }*/ + +/*jsonData := `{ + "DEVICE_METADATA": { + "localhost": { + "hwsku": "Force10-S6100", + "default_bgp_status": "up", + "docker_routing_config_mode": "unified", + "hostname": "sonic-s6100-01", + "platform": "x86_64-dell_s6100_c2538-r0", + "mac": "4c:76:25:f4:70:82", + "default_pfcwd_status": "disable", + "deployment_id": "1", + "type": "ToRRouter" + } + } + }`*/ +/*jsonData := `{ + "DEVICE_NEIGHBOR": { + "ARISTA04T1": { + "mgmt_addr": "10.20.0.163", + "hwsku": "Arista", + "lo_addr": "2.2.2.2", + "local_port": "Ethernet124", + "type": "LeafRouter", + "port": "Ethernet68" + } + } + }`*/ +/*jsonData := `{ + "BGP_NEIGHBOR": { + "10.0.0.61": { + "local_addr": "10.0.0.60", + "asn": 64015, + "name": "ARISTA15T0" + } + } + }`*/ + +/* jsonData := `{ + "INTERFACE": { + "Ethernet68|10.0.0.0/31": {}, + "Ethernet24|10.0.0.2/31": {}, + "Ethernet112|10.0.0.4/31": {} + } + }`*/ + +/*jsonData := `{ + "INTERFACE": { + "Ethernet68|10.0.0.0/31": {}, + "Ethernet24|10.0.0.2/31": {}, + "Ethernet112|10.0.0.4/31": {} + } + }`*/ +/*jsonData := `{ + "PORTCHANNEL_INTERFACE": { + "PortChannel01|10.0.0.56/31": {}, + "PortChannel01|FC00::71/126": {}, + "PortChannel02|10.0.0.58/31": {}, + "PortChannel02|FC00::75/126": {} + } + + }`*/ +/*jsonData := `{ + "VLAN_INTERFACE": { + "Vlan1000|192.168.0.1/27": {} + } + }`*/ + start := time.Now() + + dataFile := "" + if (len(os.Args) >= 2) { + if (os.Args[1] == "debug") { + cvl.Debug(true) + } else { + dataFile = os.Args[1] + } + } + if (len(os.Args) == 3) { + dataFile = os.Args[2] + } + + //cvl.Initialize() + + b, e := ioutil.ReadFile(dataFile) + if e != nil { + fmt.Printf("\nFailed to read data file : %v\n", e) + } else { + jsonData = string(b) + } + + + cv, ret := cvl.ValidationSessOpen() + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("NewDB: Could not create CVL session") + return + } + + err := cv.ValidateConfig(jsonData) + + fmt.Printf("\nValidating data = %v\n\n", jsonData); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + + keyData := make([]cvl.CVLEditConfigData, 4) + keyData[0].VType = cvl.VALIDATE_NONE + keyData[0].VOp = cvl.OP_NONE + keyData[0].Key = "ACL_TABLE|MyACL55_ACL_IPV4" + keyData[0].Data = make(map[string]string) + keyData[0].Data["stage"] = "INGRESS" + keyData[0].Data["type"] = "l3" + + keyData[1].VType = cvl.VALIDATE_NONE + keyData[1].VOp = cvl.OP_NONE + keyData[1].Key = "ACL_RULE|MyACL55_ACL_IPV4|RULE_1" + keyData[1].Data = make(map[string]string) + keyData[1].Data["packet_action"] = "forward" + keyData[1].Data["ip_protocol"] = "ip" + keyData[1].Data["src_ip"] = "10.1.1.1/32" + keyData[1].Data["dst_ip"] = "20.2.2.2/32" + + keyData[2].VType = cvl.VALIDATE_NONE + keyData[2].VOp = cvl.OP_NONE + keyData[2].Key = "ACL_TABLE|MyACL11_ACL_IPV4" + keyData[2].Data = make(map[string]string) + keyData[2].Data["stage"] = "INGRESS" + + keyData[3].VType = cvl.VALIDATE_ALL + keyData[3].VOp = cvl.OP_CREATE + keyData[3].Key = "VLAN|Vlan901" + keyData[3].Data = make(map[string]string) + keyData[3].Data["members"] = "Ethernet8" + keyData[3].Data["vlanid"] = "901" + + _, ret = cv.ValidateEditConfig(keyData) + fmt.Printf("\n\n\n cvl.ValidateEditConfig() = %d\n", ret) + + keyData1 := make([]cvl.CVLEditConfigData, 3) + keyData1[0].VType = cvl.VALIDATE_NONE + keyData1[0].VOp = cvl.OP_NONE + keyData1[0].Key = "ACL_TABLE|MyACL11_ACL_IPV4" + keyData1[0].Data = make(map[string]string) + keyData1[0].Data["stage"] = "INGRESS" + keyData1[0].Data["type"] = "l3" + + keyData1[1].VType = cvl.VALIDATE_NONE + keyData1[1].VOp = cvl.OP_NONE + keyData1[1].Key = "ACL_RULE|MyACL11_ACL_IPV4|RULE_1" + keyData1[1].Data = make(map[string]string) + keyData1[1].Data["packet_action"] = "forward" + keyData1[1].Data["ip_protocol"] = "ip" + keyData1[1].Data["src_ip"] = "10.1.1.1/32" + keyData1[1].Data["dst_ip"] = "20.2.2.2/32" + + keyData1[2].VType = cvl.VALIDATE_ALL + keyData1[2].VOp = cvl.OP_UPDATE + keyData1[2].Key = "ACL_TABLE|MyACL33_ACL_IPV4" + keyData1[2].Data = make(map[string]string) + keyData1[2].Data["stage"] = "INGRESS" + + _, ret = cv.ValidateEditConfig(keyData) + fmt.Printf("\n\n\n cvl.ValidateEditConfig() = %d\n", ret) + + + keyData2 := make([]cvl.CVLEditConfigData, 3) + keyData2[0].VType = cvl.VALIDATE_ALL + keyData2[0].VOp = cvl.OP_DELETE + keyData2[0].Key = "ACL_TABLE|MyACL11_ACL_IPV4" + keyData2[0].Data = make(map[string]string) + + keyData2[1].VType = cvl.VALIDATE_ALL + keyData2[1].VOp = cvl.OP_DELETE + keyData2[1].Key = "ACL_RULE|MyACL11_ACL_IPV4|RULE_1" + keyData2[1].Data = make(map[string]string) + + keyData2[2].VType = cvl.VALIDATE_ALL + keyData2[2].VOp = cvl.OP_DELETE + keyData2[2].Key = "ACL_TABLE|MyACL33_ACL_IPV4" + keyData2[2].Data = make(map[string]string) + + _, ret = cv.ValidateEditConfig(keyData) + fmt.Printf("\n\n\n cvl.ValidateEditConfig() = %d\n", ret) + + + cvl.ValidationSessClose(cv) + cvl.Finish() + fmt.Printf("\n\n\n Time taken = %v\n", time.Since(start)) + + stopChan := make(chan int, 1) + for { + select { + case <- stopChan: + } + } + + +} diff --git a/cvl/tests/config_db.json b/cvl/tests/config_db.json new file mode 100644 index 000000000..381412ad6 --- /dev/null +++ b/cvl/tests/config_db.json @@ -0,0 +1,107 @@ +{ + "VLAN": { + "Vlan100": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + }, + "Vlan1200": { + "members": [ + "Ethernet64", + "Ethernet8" + ], + "vlanid": "1200" + }, + "Vlan2500": { + "members": [ + "Ethernet8", + "Ethernet64" + ], + "vlanid": "2500" + } + }, + "VLAN_MEMBER": { + "Vlan100|Ethernet924": { + "tagging_mode": "tagged" + }, + "Vlan100|Ethernet28": { + "tagging_mode": "tagged" + }, + "Vlan1200|Ethernet4": { + "tagging_mode": "tagged" + } + }, + "WRED_PROFILE": { + "AZURE_LOSSLESS": { + "red_max_threshold": "312000", + "wred_green_enable": "true", + "ecn": "ecn_all", + "green_min_threshold": "104000", + "red_min_threshold": "104000", + "wred_yellow_enable": "true", + "yellow_min_threshold": "104000", + "wred_red_enable": "true", + "yellow_max_threshold": "312000", + "green_max_threshold": "312000" + } + }, + "BUFFER_POOL": { + "egress_lossless_pool": { + "type": "egress", + "mode": "static", + "size": "12766208" + }, + "egress_lossy_pool": { + "type": "egress", + "mode": "dynamic", + "size": "8072396" + }, + "ingress_lossless_pool": { + "type": "ingress", + "mode": "dynamic", + "size": "12766208" + } + }, + "MIRROR_SESSION": { + "everflow0": { + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2" + } + }, + "SCHEDULER": { + "scheduler.0": { + "type": "DWRR", + "weight": "25" + }, + "scheduler.1": { + "type": "DWRR", + "weight": "30" + }, + "scheduler.2": { + "type": "DWRR", + "weight": "20" + } + }, + "QUEUE": { + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|0": { + "scheduler": "[SCHEDULER|scheduler.1]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|1": { + "scheduler": "[SCHEDULER|scheduler.2]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|3-4": { + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]", + "scheduler": "[SCHEDULER|scheduler.0]" + } + }, + "TC_TO_QUEUE_MAP": { + "AZURE": { + "1": "1", + "0": "0", + "3": "3", + "4": "4" + } + } +} diff --git a/cvl/tests/config_db1.json b/cvl/tests/config_db1.json new file mode 100644 index 000000000..b565af7cb --- /dev/null +++ b/cvl/tests/config_db1.json @@ -0,0 +1,313 @@ +{ + "QUEUE": { + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|0": { + "scheduler": "[SCHEDULER|scheduler.1]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|1": { + "scheduler": "[SCHEDULER|scheduler.2]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|3-4": { + "wred_profile": "[WRED_PROFILE|AZURE_LOSSLESS]", + "scheduler": "[SCHEDULER|scheduler.0]" + } + }, + "PORT": { + "Ethernet0": { + "alias": "fortyGigE0/0", + "lanes": "29,30,31,32" + }, + "Ethernet4": { + "alias": "fortyGigE0/4", + "lanes": "25,26,27,28" + }, + "Ethernet8": { + "alias": "fortyGigE0/8", + "lanes": "37,38,39,40" + }, + "Ethernet12": { + "alias": "fortyGigE0/12", + "lanes": "33,34,35,36" + }, + "Ethernet16": { + "alias": "fortyGigE0/16", + "lanes": "41,42,43,44" + }, + "Ethernet20": { + "alias": "fortyGigE0/20", + "lanes": "45,46,47,48" + }, + "Ethernet24": { + "alias": "fortyGigE0/24", + "lanes": "5,6,7,8" + }, + "Ethernet28": { + "alias": "fortyGigE0/28", + "lanes": "1,2,3,4" + }, + "Ethernet32": { + "alias": "fortyGigE0/32", + "lanes": "9,10,11,12" + }, + "Ethernet36": { + "alias": "fortyGigE0/36", + "lanes": "13,14,15,16" + }, + "Ethernet40": { + "alias": "fortyGigE0/40", + "lanes": "21,22,23,24" + }, + "Ethernet44": { + "alias": "fortyGigE0/44", + "lanes": "17,18,19,20" + }, + "Ethernet48": { + "alias": "fortyGigE0/48", + "lanes": "49,50,51,52" + }, + "Ethernet52": { + "alias": "fortyGigE0/52", + "lanes": "53,54,55,56" + }, + "Ethernet56": { + "alias": "fortyGigE0/56", + "lanes": "61,62,63,64" + }, + "Ethernet60": { + "alias": "fortyGigE0/60", + "lanes": "57,58,59,60" + }, + "Ethernet64": { + "alias": "fortyGigE0/64", + "lanes": "65,66,67,68" + }, + "Ethernet68": { + "alias": "fortyGigE0/68", + "lanes": "69,70,71,72" + }, + "Ethernet72": { + "alias": "fortyGigE0/72", + "lanes": "77,78,79,80" + }, + "Ethernet76": { + "alias": "fortyGigE0/76", + "lanes": "73,74,75,76" + }, + "Ethernet80": { + "alias": "fortyGigE0/80", + "lanes": "105,106,107,108" + }, + "Ethernet84": { + "alias": "fortyGigE0/84", + "lanes": "109,110,111,112" + }, + "Ethernet88": { + "alias": "fortyGigE0/88", + "lanes": "117,118,119,120" + }, + "Ethernet92": { + "alias": "fortyGigE0/92", + "lanes": "113,114,115,116" + }, + "Ethernet96": { + "alias": "fortyGigE0/96", + "lanes": "121,122,123,124" + }, + "Ethernet100": { + "alias": "fortyGigE0/100", + "lanes": "125,126,127,128" + }, + "Ethernet104": { + "alias": "fortyGigE0/104", + "lanes": "85,86,87,88" + }, + "Ethernet108": { + "alias": "fortyGigE0/108", + "lanes": "81,82,83,84" + }, + "Ethernet112": { + "alias": "fortyGigE0/112", + "lanes": "89,90,91,92" + }, + "Ethernet116": { + "alias": "fortyGigE0/116", + "lanes": "93,94,95,96" + }, + "Ethernet120": { + "alias": "fortyGigE0/120", + "lanes": "97,98,99,100" + }, + "Ethernet124": { + "alias": "fortyGigE0/124", + "lanes": "101,102,103,104" + } + }, + "WRED_PROFILE": { + "AZURE_LOSSLESS": { + "red_max_threshold": "312000", + "wred_green_enable": "true", + "ecn": "ecn_all", + "green_min_threshold": "104000", + "red_min_threshold": "104000", + "wred_yellow_enable": "true", + "yellow_min_threshold": "104000", + "wred_red_enable": "true", + "yellow_max_threshold": "312000", + "green_max_threshold": "312000" + } + }, + "SCHEDULER": { + "scheduler.0": { + "type": "DWRR", + "weight": "25" + }, + "scheduler.1": { + "type": "DWRR", + "weight": "30" + }, + "scheduler.2": { + "type": "DWRR", + "weight": "20" + } + }, + "BUFFER_PG": { + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|0-1": { + "profile": "[BUFFER_PROFILE|ingress_lossy_profile]" + }, + "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124|3-4": { + "profile": "[BUFFER_PROFILE|ingress_lossless_profile]" + } + }, + "BUFFER_PROFILE": { + "egress_lossless_profile": { + "static_th": "12766208", + "pool": "[BUFFER_POOL|egress_lossless_pool]", + "size": "0" + }, + "egress_lossy_profile": { + "dynamic_th": "3", + "pool": "[BUFFER_POOL|egress_lossy_pool]", + "size": "1518" + }, + "ingress_lossless_profile": { + "xon_offset": "2496", + "dynamic_th": "-4", + "xon": "18432", + "xoff": "40560", + "pool": "[BUFFER_POOL|ingress_lossless_pool]", + "size": "41808" + }, + "ingress_lossy_profile": { + "dynamic_th": "3", + "pool": "[BUFFER_POOL|ingress_lossless_pool]", + "size": "0" + } + }, + "BUFFER_POOL": { + "egress_lossless_pool": { + "type": "egress", + "mode": "static", + "size": "12766208" + }, + "egress_lossy_pool": { + "type": "egress", + "mode": "dynamic", + "size": "8072396" + }, + "ingress_lossless_pool": { + "type": "ingress", + "mode": "dynamic", + "size": "12766208" + } + }, + "MIRROR_SESSION": { + "everflow0": { + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2" + } + }, + "TC_TO_QUEUE_MAP": { + "AZURE": { + "1": "1", + "0": "0", + "3": "3", + "4": "4" + } + }, + "DSCP_TO_TC_MAP": { + "AZURE": { + "56": "0", + "54": "0", + "28": "0", + "48": "0", + "29": "0", + "60": "0", + "61": "0", + "62": "0", + "63": "0", + "49": "0", + "34": "0", + "24": "0", + "25": "0", + "26": "0", + "27": "0", + "20": "0", + "21": "0", + "22": "0", + "23": "0", + "46": "0", + "47": "0", + "44": "0", + "45": "0", + "42": "0", + "43": "0", + "40": "0", + "41": "0", + "1": "0", + "0": "0", + "3": "3", + "2": "0", + "5": "0", + "4": "4", + "7": "0", + "6": "0", + "9": "0", + "8": "1", + "35": "0", + "13": "0", + "12": "0", + "15": "0", + "58": "0", + "11": "0", + "10": "0", + "39": "0", + "38": "0", + "59": "0", + "14": "0", + "17": "0", + "16": "0", + "19": "0", + "18": "0", + "31": "0", + "30": "0", + "51": "0", + "36": "0", + "53": "0", + "52": "0", + "33": "0", + "55": "0", + "37": "0", + "32": "0", + "57": "0", + "50": "0" + } + }, + "TC_TO_PRIORITY_GROUP_MAP": { + "AZURE": { + "1": "1", + "0": "0", + "3": "3", + "4": "4" + } + } +} + diff --git a/cvl/tests/config_db2.json b/cvl/tests/config_db2.json new file mode 100644 index 000000000..e6be83ac4 --- /dev/null +++ b/cvl/tests/config_db2.json @@ -0,0 +1,3437 @@ +{ + "VLAN_MEMBER": { + "Vlan51|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan51|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan97|Ethernet4": { + "tagging_mode": "untagged" + }, + "Vlan99|Ethernet4": { + "tagging_mode": "untagged" + }, + "Vlan99|Ethernet108": { + "tagging_mode": "untagged" + }, + "Vlan99|Ethernet124": { + "tagging_mode": "untagged" + }, + "Vlan101|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan101|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan101|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan102|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan102|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan102|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan103|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan103|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan103|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan104|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan104|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan104|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan105|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan105|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan105|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan106|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan106|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan106|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan107|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan107|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan107|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan108|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan108|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan108|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan109|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan109|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan109|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan110|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan110|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan110|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan111|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan111|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan111|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan112|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan112|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan112|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan113|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan113|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan113|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan114|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan114|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan114|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan115|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan115|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan115|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan116|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan116|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan116|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan117|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan117|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan117|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan118|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan118|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan118|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan119|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan119|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan119|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan120|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan120|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan120|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan121|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan121|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan121|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan122|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan122|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan122|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan123|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan123|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan123|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan124|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan124|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan124|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan125|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan125|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan125|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan126|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan126|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan126|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan127|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan127|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan127|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan128|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan128|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan128|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan129|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan129|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan129|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan130|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan130|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan130|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan131|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan131|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan131|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan132|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan132|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan132|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan133|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan133|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan133|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan134|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan134|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan134|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan135|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan135|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan135|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan136|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan136|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan136|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan137|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan137|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan137|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan138|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan138|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan138|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan139|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan139|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan139|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan140|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan140|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan140|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan141|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan141|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan141|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan142|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan142|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan142|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan143|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan143|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan143|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan144|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan144|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan144|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan145|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan145|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan145|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan146|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan146|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan146|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan147|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan147|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan147|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan148|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan148|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan148|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan149|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan149|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan149|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan150|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan150|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan150|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan151|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan151|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan151|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan152|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan152|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan152|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan153|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan153|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan153|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan154|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan154|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan154|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan155|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan155|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan155|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan156|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan156|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan156|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan157|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan157|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan157|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan158|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan158|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan158|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan159|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan159|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan159|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan160|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan160|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan160|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan161|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan161|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan161|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan162|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan162|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan162|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan163|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan163|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan163|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan164|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan164|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan164|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan165|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan165|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan165|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan166|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan166|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan166|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan167|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan167|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan167|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan168|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan168|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan168|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan169|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan169|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan169|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan170|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan170|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan170|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan171|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan171|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan171|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan172|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan172|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan172|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan173|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan173|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan173|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan174|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan174|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan174|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan175|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan175|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan175|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan176|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan176|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan176|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan177|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan177|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan177|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan178|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan178|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan178|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan179|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan179|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan179|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan180|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan180|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan180|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan181|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan181|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan181|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan182|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan182|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan182|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan183|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan183|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan183|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan184|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan184|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan184|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan185|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan185|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan185|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan186|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan186|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan186|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan187|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan187|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan187|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan188|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan188|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan188|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan189|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan189|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan189|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan190|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan190|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan190|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan191|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan191|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan191|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan192|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan192|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan192|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan193|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan193|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan193|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan194|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan194|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan194|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan195|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan195|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan195|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan196|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan196|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan196|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan197|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan197|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan197|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan198|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan198|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan198|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan199|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan200|Ethernet64": { + "tagging_mode": "tagged" + }, + "Vlan200|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan200|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan201|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan201|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan201|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan202|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan202|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan202|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan203|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan203|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan203|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan204|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan204|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan204|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan205|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan205|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan205|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan206|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan206|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan206|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan207|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan207|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan207|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan208|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan208|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan208|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan209|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan209|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan209|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan210|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan210|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan210|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan211|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan211|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan211|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan212|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan212|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan212|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan213|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan213|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan213|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan214|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan214|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan214|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan215|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan215|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan215|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan216|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan216|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan216|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan217|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan217|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan217|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan218|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan218|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan218|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan219|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan219|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan219|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan220|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan220|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan220|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan221|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan221|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan221|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan222|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan222|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan222|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan223|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan223|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan223|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan224|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan224|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan224|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan225|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan225|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan225|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan226|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan226|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan226|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan227|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan227|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan227|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan228|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan228|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan228|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan229|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan229|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan229|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan230|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan230|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan230|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan231|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan231|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan231|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan232|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan232|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan232|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan233|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan233|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan233|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan234|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan234|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan234|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan235|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan235|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan235|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan236|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan236|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan236|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan237|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan237|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan237|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan238|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan238|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan238|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan239|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan239|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan239|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan240|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan240|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan240|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan241|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan241|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan241|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan242|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan242|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan242|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan243|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan243|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan243|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan244|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan244|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan244|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan245|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan245|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan245|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan246|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan246|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan246|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan247|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan247|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan247|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan248|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan248|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan248|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan249|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan249|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan249|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan250|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan250|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan250|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan251|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan251|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan251|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan252|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan252|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan252|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan253|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan253|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan253|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan254|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan254|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan254|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan255|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan255|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan255|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan256|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan256|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan256|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan257|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan257|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan257|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan258|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan258|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan258|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan259|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan259|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan259|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan260|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan260|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan260|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan261|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan261|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan261|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan262|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan262|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan262|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan263|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan263|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan263|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan264|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan264|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan264|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan265|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan265|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan265|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan266|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan266|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan266|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan267|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan267|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan267|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan268|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan268|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan268|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan269|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan269|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan269|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan270|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan270|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan270|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan271|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan271|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan271|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan272|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan272|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan272|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan273|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan273|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan273|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan274|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan274|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan274|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan275|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan275|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan275|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan276|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan276|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan276|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan277|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan277|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan277|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan278|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan278|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan278|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan279|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan279|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan279|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan280|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan280|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan280|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan281|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan281|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan281|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan282|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan282|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan282|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan283|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan283|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan283|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan284|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan284|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan284|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan285|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan285|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan285|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan286|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan286|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan286|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan287|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan287|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan287|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan288|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan288|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan288|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan289|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan289|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan289|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan290|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan290|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan290|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan291|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan291|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan291|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan292|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan292|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan292|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan293|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan293|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan293|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan294|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan294|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan294|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan295|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan295|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan295|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan296|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan296|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan296|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan297|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan297|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan297|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan298|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan298|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan298|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan299|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan299|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan299|Ethernet4": { + "tagging_mode": "tagged" + }, + "Vlan300|Ethernet108": { + "tagging_mode": "tagged" + }, + "Vlan300|Ethernet124": { + "tagging_mode": "tagged" + }, + "Vlan300|Ethernet4": { + "tagging_mode": "tagged" + } + }, + "VLAN": { + "Vlan51": { + "members": [ + "Ethernet108", + "Ethernet124" + ], + "vlanid": "51" + }, + "Vlan97": { + "members": [ + "Ethernet4" + ], + "vlanid": "97" + }, + "Vlan99": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "99" + }, + "Vlan101": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "101" + }, + "Vlan102": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "102" + }, + "Vlan103": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "103" + }, + "Vlan104": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "104" + }, + "Vlan105": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "105" + }, + "Vlan106": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "106" + }, + "Vlan107": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "107" + }, + "Vlan108": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "108" + }, + "Vlan109": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "109" + }, + "Vlan110": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "110" + }, + "Vlan111": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "111" + }, + "Vlan112": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "112" + }, + "Vlan113": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "113" + }, + "Vlan114": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "114" + }, + "Vlan115": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "115" + }, + "Vlan116": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "116" + }, + "Vlan117": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "117" + }, + "Vlan118": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "118" + }, + "Vlan119": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "119" + }, + "Vlan120": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "120" + }, + "Vlan121": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "121" + }, + "Vlan122": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "122" + }, + "Vlan123": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "123" + }, + "Vlan124": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "124" + }, + "Vlan125": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "125" + }, + "Vlan126": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "126" + }, + "Vlan127": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "127" + }, + "Vlan128": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "128" + }, + "Vlan129": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "129" + }, + "Vlan130": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "130" + }, + "Vlan131": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "131" + }, + "Vlan132": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "132" + }, + "Vlan133": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "133" + }, + "Vlan134": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "134" + }, + "Vlan135": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "135" + }, + "Vlan136": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "136" + }, + "Vlan137": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "137" + }, + "Vlan138": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "138" + }, + "Vlan139": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "139" + }, + "Vlan140": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "140" + }, + "Vlan141": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "141" + }, + "Vlan142": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "142" + }, + "Vlan143": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "143" + }, + "Vlan144": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "144" + }, + "Vlan145": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "145" + }, + "Vlan146": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "146" + }, + "Vlan147": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "147" + }, + "Vlan148": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "148" + }, + "Vlan149": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "149" + }, + "Vlan150": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "150" + }, + "Vlan151": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "151" + }, + "Vlan152": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "152" + }, + "Vlan153": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "153" + }, + "Vlan154": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "154" + }, + "Vlan155": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "155" + }, + "Vlan156": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "156" + }, + "Vlan157": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "157" + }, + "Vlan158": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "158" + }, + "Vlan159": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "159" + }, + "Vlan160": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "160" + }, + "Vlan161": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "161" + }, + "Vlan162": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "162" + }, + "Vlan163": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "163" + }, + "Vlan164": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "164" + }, + "Vlan165": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "165" + }, + "Vlan166": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "166" + }, + "Vlan167": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "167" + }, + "Vlan168": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "168" + }, + "Vlan169": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "169" + }, + "Vlan170": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "170" + }, + "Vlan171": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "171" + }, + "Vlan172": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "172" + }, + "Vlan173": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "173" + }, + "Vlan174": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "174" + }, + "Vlan175": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "175" + }, + "Vlan176": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "176" + }, + "Vlan177": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "177" + }, + "Vlan178": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "178" + }, + "Vlan179": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "179" + }, + "Vlan180": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "180" + }, + "Vlan181": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "181" + }, + "Vlan182": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "182" + }, + "Vlan183": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "183" + }, + "Vlan184": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "184" + }, + "Vlan185": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "185" + }, + "Vlan186": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "186" + }, + "Vlan187": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "187" + }, + "Vlan188": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "188" + }, + "Vlan189": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "189" + }, + "Vlan190": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "190" + }, + "Vlan191": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "191" + }, + "Vlan192": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "192" + }, + "Vlan193": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "193" + }, + "Vlan194": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "194" + }, + "Vlan195": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "195" + }, + "Vlan196": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "196" + }, + "Vlan197": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "197" + }, + "Vlan198": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "198" + }, + "Vlan199": { + "members": [ + "Ethernet64" + ], + "vlanid": "199" + }, + "Vlan200": { + "members": [ + "Ethernet64", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "200" + }, + "Vlan201": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "201" + }, + "Vlan202": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "202" + }, + "Vlan203": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "203" + }, + "Vlan204": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "204" + }, + "Vlan205": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "205" + }, + "Vlan206": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "206" + }, + "Vlan207": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "207" + }, + "Vlan208": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "208" + }, + "Vlan209": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "209" + }, + "Vlan210": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "210" + }, + "Vlan211": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "211" + }, + "Vlan212": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "212" + }, + "Vlan213": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "213" + }, + "Vlan214": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "214" + }, + "Vlan215": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "215" + }, + "Vlan216": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "216" + }, + "Vlan217": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "217" + }, + "Vlan218": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "218" + }, + "Vlan219": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "219" + }, + "Vlan220": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "220" + }, + "Vlan221": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "221" + }, + "Vlan222": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "222" + }, + "Vlan223": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "223" + }, + "Vlan224": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "224" + }, + "Vlan225": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "225" + }, + "Vlan226": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "226" + }, + "Vlan227": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "227" + }, + "Vlan228": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "228" + }, + "Vlan229": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "229" + }, + "Vlan230": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "230" + }, + "Vlan231": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "231" + }, + "Vlan232": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "232" + }, + "Vlan233": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "233" + }, + "Vlan234": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "234" + }, + "Vlan235": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "235" + }, + "Vlan236": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "236" + }, + "Vlan237": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "237" + }, + "Vlan238": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "238" + }, + "Vlan239": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "239" + }, + "Vlan240": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "240" + }, + "Vlan241": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "241" + }, + "Vlan242": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "242" + }, + "Vlan243": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "243" + }, + "Vlan244": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "244" + }, + "Vlan245": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "245" + }, + "Vlan246": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "246" + }, + "Vlan247": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "247" + }, + "Vlan248": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "248" + }, + "Vlan249": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "249" + }, + "Vlan250": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "250" + }, + "Vlan251": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "251" + }, + "Vlan252": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "252" + }, + "Vlan253": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "253" + }, + "Vlan254": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "254" + }, + "Vlan255": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "255" + }, + "Vlan256": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "256" + }, + "Vlan257": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "257" + }, + "Vlan258": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "258" + }, + "Vlan259": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "259" + }, + "Vlan260": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "260" + }, + "Vlan261": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "261" + }, + "Vlan262": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "262" + }, + "Vlan263": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "263" + }, + "Vlan264": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "264" + }, + "Vlan265": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "265" + }, + "Vlan266": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "266" + }, + "Vlan267": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "267" + }, + "Vlan268": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "268" + }, + "Vlan269": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "269" + }, + "Vlan270": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "270" + }, + "Vlan271": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "271" + }, + "Vlan272": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "272" + }, + "Vlan273": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "273" + }, + "Vlan274": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "274" + }, + "Vlan275": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "275" + }, + "Vlan276": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "276" + }, + "Vlan277": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "277" + }, + "Vlan278": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "278" + }, + "Vlan279": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "279" + }, + "Vlan280": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "280" + }, + "Vlan281": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "281" + }, + "Vlan282": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "282" + }, + "Vlan283": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "283" + }, + "Vlan284": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "284" + }, + "Vlan285": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "285" + }, + "Vlan286": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "286" + }, + "Vlan287": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "287" + }, + "Vlan288": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "288" + }, + "Vlan289": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "289" + }, + "Vlan290": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "290" + }, + "Vlan291": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "291" + }, + "Vlan292": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "292" + }, + "Vlan293": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "293" + }, + "Vlan294": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "294" + }, + "Vlan295": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "295" + }, + "Vlan296": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "296" + }, + "Vlan297": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "297" + }, + "Vlan298": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "298" + }, + "Vlan299": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "299" + }, + "Vlan300": { + "members": [ + "Ethernet4", + "Ethernet108", + "Ethernet124" + ], + "vlanid": "300" + } + } +} diff --git a/cvl/tests/create_acl_table.json b/cvl/tests/create_acl_table.json new file mode 100644 index 000000000..604be2e2d --- /dev/null +++ b/cvl/tests/create_acl_table.json @@ -0,0 +1,8 @@ +{ +"ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + } +} diff --git a/cvl/tests/cv_acl.go b/cvl/tests/cv_acl.go new file mode 100644 index 000000000..00637582a --- /dev/null +++ b/cvl/tests/cv_acl.go @@ -0,0 +1,443 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package main + +import ( + "fmt" + "time" + "os" + "github.com/Azure/sonic-mgmt-common/cvl" + "github.com/go-redis/redis" + "strconv" +) + +func getConfigDbClient() *redis.Client { + rclient := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 4, + DialTimeout: 0, + }) + _, err := rclient.Ping().Result() + if err != nil { + fmt.Printf("failed to connect to redis server %v", err) + } + return rclient +} + +/* Unloads the Config DB based on JSON File. */ +func unloadConfigDB(rclient *redis.Client, key string, data map[string]string) { + _, err := rclient.Del(key).Result() + + if err != nil { + fmt.Printf("Failed to delete for key %s, data %v, err %v", key, data, err) + } + +} + +/* Loads the Config DB based on JSON File. */ +func loadConfigDB(rclient *redis.Client, key string, data map[string]string) { + + dataTmp := make(map[string]interface{}) + + for k, v := range data { + dataTmp[k] = v + } + + _, err := rclient.HMSet(key, dataTmp).Result() + + if err != nil { + fmt.Printf("Failed to add for key %s, data %v, err %v", key, data, err) + } + +} + +func main() { + start := time.Now() + count := 0 + + cvl.Initialize() + + if ((len(os.Args) > 1) && (os.Args[1] == "debug")) { + cvl.Debug(true) + } + + rclient := getConfigDbClient() + + if ((len(os.Args) > 1) && (os.Args[1] == "add")) { + + //Add ACL + aclNoStart, _ := strconv.Atoi(os.Args[2]) + aclNoEnd, _ := strconv.Atoi(os.Args[3]) + for aclNum:= aclNoStart ;aclNum <= aclNoEnd; aclNum++ { + aclNo := fmt.Sprintf("%d", aclNum) + + cvSess, _ := cvl.ValidationSessOpen() + + cfgDataAclRule := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + fmt.Sprintf("ACL_TABLE|TestACL%s", aclNo), + map[string]string { + "stage": "INGRESS", + "type": "L3", + //"ports@": "Ethernet0", + }, + }, + } + + _, ret := cvSess.ValidateEditConfig(cfgDataAclRule) + + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + return + } + + cfgDataAclRule[0].VType = cvl.VALIDATE_NONE + + //Create 7 ACL rules + for i:=0; i<7; i++ { + cfgDataAclRule = append(cfgDataAclRule, cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + fmt.Sprintf("ACL_RULE|TestACL%s|Rule%d", aclNo, i+1), + map[string]string { + "PACKET_ACTION": "FORWARD", + "IP_TYPE": "IPV4", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": fmt.Sprintf("%d", 201 + i), + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT": fmt.Sprintf("%d", 701 + i), + }, + }) + + _, ret1 := cvSess.ValidateEditConfig(cfgDataAclRule) + if (ret1 != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + return + } + + cfgDataAclRule[1 + i].VType = cvl.VALIDATE_NONE + } + + //Write to DB + for _, cfgDataItem := range cfgDataAclRule { + loadConfigDB(rclient, cfgDataItem.Key, cfgDataItem.Data) + } + + cvl.ValidationSessClose(cvSess) + } + + return + } else if ((len(os.Args) > 1) && (os.Args[1] == "del")) { + aclNoStart, _ := strconv.Atoi(os.Args[2]) + aclNoEnd, _ := strconv.Atoi(os.Args[3]) + for aclNum:= aclNoStart ;aclNum <= aclNoEnd; aclNum++ { + aclNo := fmt.Sprintf("%d", aclNum) + cvSess,_ := cvl.ValidationSessOpen() + + //Delete ACL + + cfgDataAclRule := []cvl.CVLEditConfigData{} + + //Create 7 ACL rules + for i:=0; i<7; i++ { + cfgDataAclRule = append(cfgDataAclRule, cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + fmt.Sprintf("ACL_RULE|TestACL%s|Rule%d", aclNo, i+1), + map[string]string { + }, + }) + + _, ret := cvSess.ValidateEditConfig(cfgDataAclRule) + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + return + } + + cfgDataAclRule[i].VType = cvl.VALIDATE_NONE + } + + cfgDataAclRule = append(cfgDataAclRule, cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + fmt.Sprintf("ACL_TABLE|TestACL%s", aclNo), + map[string]string { + }, + }) + + _, ret := cvSess.ValidateEditConfig(cfgDataAclRule) + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + return + } + + //Write to DB + for _, cfgDataItem := range cfgDataAclRule { + unloadConfigDB(rclient, cfgDataItem.Key, cfgDataItem.Data) + } + + cvl.ValidationSessClose(cvSess) + } + + return + } + + cv, ret := cvl.ValidationSessOpen() + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Could not create CVL session") + return + } + + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + }, + "TestACL2": { + "stage": "EGRESS", + "ports": "Ethernet4" + } + } + }` + + fmt.Printf("\nValidating data = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL2": { + "stage": "EGRESS", + "ports": "Ethernet804" + } + } + }` + + + fmt.Printf("\nValidating data for external dependency check = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "type": "L3" + } + } + }` + + + fmt.Printf("\nValidating data for mandatory element misssing = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + }, + "ACL_RULE": { + "TestACL1|Rule1": { + "PACKET_ACTION": "FORWARD", + "IP_PROTOCOL": "103", + "SRC_IP": "10.1.1.1/32", + "DST_IP": "20.2.2.2/32" + } + } + }` + + + + fmt.Printf("\nValidating data for internal dependency check = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + }, + "ACL_RULE": { + "TestACL1|Rule1": { + "PACKET_ACTION": "FORWARD", + "IP_PROTOCOL": "103" + } + } + }` + + + + fmt.Printf("\nValidating data for mandatory element check = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + }, + "ACL_RULE": { + "TestACL1|Rule1": { + "PACKET_ACTION": "FORWARD", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32" + } + } + }` + + + + fmt.Printf("\nValidating data for mandatory element check = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + }, + "ACL_RULE": { + "TestACL1|Rule1": { + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": 8080, + "ETHER_TYPE":"0x0800", + "IP_PROTOCOL": "1", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000" + } + } + }` + + + + fmt.Printf("\nValidating data for pattern check = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "ACL_TABLE": { + "TestACL1": { + "stage": "INGRESS", + "type": "L3" + } + }, + "ACL_RULE": { + "TestACL1|Rule1": { + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "ABC", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000" + } + } + }` + + + + fmt.Printf("\nValidating data for type check = %v\n\n", jsonData); + + err := cv.ValidateConfig(jsonData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + + cvl.ValidationSessClose(cv) + + cvl.Finish() + + fmt.Printf("\n\n\n Time taken for %v requests = %v\n", count, time.Since(start)) +} diff --git a/cvl/tests/cv_edit_op.go b/cvl/tests/cv_edit_op.go new file mode 100644 index 000000000..b77270941 --- /dev/null +++ b/cvl/tests/cv_edit_op.go @@ -0,0 +1,192 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package main + +import ( + "fmt" + "time" + "os" + "github.com/Azure/sonic-mgmt-common/cvl" +) + +func main() { + start := time.Now() + count := 0 + + cvl.Initialize() + cv, _ := cvl.ValidationSessOpen() + + if ((len(os.Args) > 1) && (os.Args[1] == "debug")) { + cvl.Debug(true) + } + + { + count++ + + cfgData := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "ACL_TABLE|TestACL1", + map[string]string { + "stage": "INGRESS", + "type": "L3", + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "ACL_RULE|TestACL1|Rule1", + map[string]string { + "PACKET_ACTION": "FORWARD", + "SRC_IP": "10.1.1.1/32", + "L4_SRC_PORT": "1909", + "IP_PROTOCOL": "103", + "DST_IP": "20.2.2.2/32", + "L4_DST_PORT_RANGE": "9000-12000", + }, + }, + } + + fmt.Printf("\n\n%d. Validating create data = %v\n\n", count, cfgData); + + _, err := cv.ValidateEditConfig(cfgData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + + cfgData := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_TABLE|MyACL11_ACL_IPV4", + map[string]string { + "stage": "INGRESS", + "type": "MIRROR", + }, + }, + } + + fmt.Printf("\n\n%d. Validating update data = %v\n\n", count, cfgData); + + _, err := cv.ValidateEditConfig(cfgData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + cfgData := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "MIRROR_SESSION|everflow", + map[string]string { + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2", + }, + }, + } + + fmt.Printf("\n\n%d. Validating create data = %v\n\n", count, cfgData); + _, err := cv.ValidateEditConfig(cfgData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + + count++ + cfgData = []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "MIRROR_SESSION|everflow", + map[string]string { + "src_ip": "10.1.0.32", + "dst_ip": "2.2.2.2", + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_UPDATE, + "ACL_RULE|MyACL11_ACL_IPV4|RULE_1", + map[string]string { + "MIRROR_ACTION": "everflow", + }, + }, + } + + fmt.Printf("\n\n%d. Validating data for update = %v\n\n", count, cfgData); + + _, err = cv.ValidateEditConfig(cfgData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + + cfgData := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "MIRROR_SESSION|everflow", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_RULE|MyACL11_ACL_IPV4|RULE_1", + map[string]string { + }, + }, + } + + fmt.Printf("\n\n%d. Validating data for delete = %v\n\n", count, cfgData); + + _, err := cv.ValidateEditConfig(cfgData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + + cvl.ValidationSessClose(cv) + cvl.Finish() + + fmt.Printf("\n\n\n Time taken for %v requests = %v\n", count, time.Since(start)) +} diff --git a/cvl/tests/cv_vlan.go b/cvl/tests/cv_vlan.go new file mode 100644 index 000000000..f69f41960 --- /dev/null +++ b/cvl/tests/cv_vlan.go @@ -0,0 +1,448 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package main + + +import ( + "fmt" + "os" + "time" + "github.com/Azure/sonic-mgmt-common/cvl" + "github.com/go-redis/redis" + "strconv" +) + +func getConfigDbClient() *redis.Client { + rclient := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 4, + DialTimeout: 0, + }) + _, err := rclient.Ping().Result() + if err != nil { + fmt.Printf("failed to connect to redis server %v", err) + } + return rclient +} + +/* Unloads the Config DB based on JSON File. */ +func unloadConfigDB(rclient *redis.Client, key string, data map[string]string) { + _, err := rclient.Del(key).Result() + + if err != nil { + fmt.Printf("Failed to delete for key %s, data %v, err %v", key, data, err) + } + +} + +/* Loads the Config DB based on JSON File. */ +func loadConfigDB(rclient *redis.Client, key string, data map[string]string) { + + dataTmp := make(map[string]interface{}) + + for k, v := range data { + dataTmp[k] = v + } + + _, err := rclient.HMSet(key, dataTmp).Result() + + if err != nil { + fmt.Printf("Failed to add for key %s, data %v, err %v", key, data, err) + } + +} + +func main() { + start := time.Now() + count := 0 + + cvl.Initialize() + if ((len(os.Args) > 1) && (os.Args[1] == "debug")) { + cvl.Debug(true) + } + + rclient := getConfigDbClient() + + if ((len(os.Args) > 1) && (os.Args[1] == "add")) { + + //Add ACL + vlanNoStart, _ := strconv.Atoi(os.Args[2]) + vlanNoEnd, _ := strconv.Atoi(os.Args[3]) + for vlanNum:= vlanNoStart ;vlanNum <= vlanNoEnd; vlanNum++ { + cvSess, _ := cvl.ValidationSessOpen() + + cfgDataVlan := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + fmt.Sprintf("VLAN|Vlan%d", vlanNum), + map[string]string { + "vlanid": fmt.Sprintf("%d", vlanNum), + "members@": "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28", + }, + }, + } + + _, ret := cvSess.ValidateEditConfig(cfgDataVlan) + + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + return + } + + cfgDataVlan[0].VType = cvl.VALIDATE_NONE + + for i:=0; i<7; i++ { + cfgDataVlan = append(cfgDataVlan, cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + fmt.Sprintf("VLAN_MEMBER|Vlan%d|Ethernet%d", vlanNum, i * 4), + map[string]string { + "tagging_mode" : "tagged", + }, + }) + + _, ret1 := cvSess.ValidateEditConfig(cfgDataVlan) + if (ret1 != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + return + } + + cfgDataVlan[1 + i].VType = cvl.VALIDATE_NONE + } + + //Write to DB + for _, cfgDataItem := range cfgDataVlan { + loadConfigDB(rclient, cfgDataItem.Key, cfgDataItem.Data) + } + + cvl.ValidationSessClose(cvSess) + } + + return + } else if ((len(os.Args) > 1) && (os.Args[1] == "del")) { + vlanNoStart, _ := strconv.Atoi(os.Args[2]) + vlanNoEnd, _ := strconv.Atoi(os.Args[3]) + for vlanNum:= vlanNoStart ;vlanNum <= vlanNoEnd; vlanNum++ { + cvSess,_ := cvl.ValidationSessOpen() + + //Delete ACL + + cfgDataVlan := []cvl.CVLEditConfigData{} + + //Create 7 ACL rules + for i:=0; i<7; i++ { + cfgDataVlan = append(cfgDataVlan, cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + fmt.Sprintf("VLAN_MEMBER|Vlan%d|Ethernet%d", vlanNum, i * 4), + map[string]string { + }, + }) + + _, ret := cvSess.ValidateEditConfig(cfgDataVlan) + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + //return + } + + cfgDataVlan[i].VType = cvl.VALIDATE_NONE + } + + cfgDataVlan = append(cfgDataVlan, cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + fmt.Sprintf("VLAN|Vlan%d", vlanNum), + map[string]string { + }, + }) + + _, ret := cvSess.ValidateEditConfig(cfgDataVlan) + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("Validation failure\n") + //return + } + + //Write to DB + for _, cfgDataItem := range cfgDataVlan { + unloadConfigDB(rclient, cfgDataItem.Key, cfgDataItem.Data) + } + + cvl.ValidationSessClose(cvSess) + } + return + } + cv, ret := cvl.ValidationSessOpen() + if (ret != cvl.CVL_SUCCESS) { + fmt.Printf("NewDB: Could not create CVL session") + return + } + + { + count++ + keyData := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL|ch1", + map[string]string { + "admin_status": "up", + "mtu": "9100", + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL|ch2", + map[string]string { + "admin_status": "up", + "mtu": "9100", + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch1|Ethernet4", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch1|Ethernet8", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch2|Ethernet12", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch2|Ethernet16", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_NONE, + cvl.OP_NONE, + "PORTCHANNEL_MEMBER|ch2|Ethernet20", + map[string]string { + }, + }, + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_CREATE, + "VLAN|Vlan1001", + map[string]string { + "vlanid": "1001", + "members@": "Ethernet24,ch1,Ethernet8", + }, + }, + } + + fmt.Printf("\nValidating data for must = %v\n\n", keyData); + + _, err := cv.ValidateEditConfig(keyData) + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + + } + + { + keyData := []cvl.CVLEditConfigData { + cvl.CVLEditConfigData { + cvl.VALIDATE_ALL, + cvl.OP_DELETE, + "ACL_TABLE|MyACL1_ACL_IPV4", + map[string]string { + "type": "L3", + }, + }, + } + + _, err := cv.ValidateEditConfig(keyData) + + fmt.Printf("\nValidating field delete...\n\n"); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + + } + + { + count++ + jsonData :=`{ + "VLAN": { + "Vlan100": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + } + } + }` + + + err := cv.ValidateConfig(jsonData) + + fmt.Printf("\nValidating data = %v\n\n", jsonData); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + + { + count++ + jsonData :=`{ + "VLAN": { + "Vln100": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + } + } + }` + + + err := cv.ValidateConfig(jsonData) + + fmt.Printf("\nValidating data for key syntax = %v\n\n", jsonData); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + + { + count++ + jsonData :=`{ + "VLAN": { + "Vlan4096": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + } + } + }` + + + err := cv.ValidateConfig(jsonData) + + fmt.Printf("\nValidating data for range check = %v\n\n", jsonData); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + jsonData :=`{ + "VLAN": { + "Vlan201": { + "members": [ + "Ethernet44", + "Ethernet64" + ], + "vlanid": "100" + } + } + }` + + + err := cv.ValidateConfig(jsonData) + + fmt.Printf("\nValidating data for internal dependency check = %v\n\n", jsonData); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + { + count++ + /*jsonData :=`{ + "VLAN": { + "Vlan100": { + "members": [ + "Ethernet44", + "Ethernet964" + ], + "vlanid": "100" + }, + "Vlan1200": { + "members": [ + "Ethernet64", + "Ethernet1008" + ], + "vlanid": "1200" + } + } + }`*/ + jsonData :=`{ + "VLAN": { + "Vlan4095": { + "vlanid": "4995" + } + } + }` + + err := cv.ValidateConfig(jsonData) + + fmt.Printf("\nValidating data for external dependency check = %v\n\n", jsonData); + + if (err == cvl.CVL_SUCCESS) { + fmt.Printf("\nConfig Validation succeeded.\n\n"); + } else { + fmt.Printf("\nConfig Validation failed.\n\n"); + } + } + + cvl.ValidationSessClose(cv) + + cvl.Finish() + + fmt.Printf("\n\n\n Time taken for %v requests = %v\n", count, time.Since(start)) +} diff --git a/cvl/tests/run_test.sh b/cvl/tests/run_test.sh new file mode 100755 index 000000000..25bfe0d96 --- /dev/null +++ b/cvl/tests/run_test.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +profiling="" +testcase="" +coverpkgs="-coverpkg=github.com/Azure/sonic-mgmt-common/cvl,github.com/Azure/sonic-mgmt-common/cvl/internal/util,github.com/Azure/sonic-mgmt-common/cvl/internal/yparser" + +if [ "${BUILD}:" != ":" ] ; then + go test -mod=vendor -v -c -gcflags="all=-N -l" +fi + +if [ "${TESTCASE}:" != ":" ] ; then + testcase="-run ${TESTCASE}" +fi + +if [ "${PROFILE}:" != ":" ] ; then + profiling="-bench=. -benchmem -cpuprofile profile.out" +fi + +#Run test and display report +if [ "${NOREPORT}:" != ":" ] ; then + go test -mod=vendor -v -cover ${coverpkgs} ${testcase} +elif [ "${COVERAGE}:" != ":" ] ; then + go test -mod=vendor -v -cover -coverprofile coverage.out ${coverpkgs} ${testcase} + go tool cover -html=coverage.out +else + go test -mod=vendor -v -cover -json ${profiling} ${testcase} | tparse -smallscreen -all +fi + +#With profiling +#go test -v -cover -json -bench=. -benchmem -cpuprofile profile.out | tparse -smallscreen -all + diff --git a/debian/.gitignore b/debian/.gitignore new file mode 100644 index 000000000..82d5a52ac --- /dev/null +++ b/debian/.gitignore @@ -0,0 +1,9 @@ +.debhelper/ +*.debhelper +*.debhelper.log +*.substvars +sonic-mgmt-common/ +sonic-mgmt-common-codegen/ +sonic-host-service/ +tmp/ +files diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 000000000..bdb8cf788 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +sonic-mgmt-common (1.0.0) UNRELEASED; urgency=low + + * Initial release. + + -- Sachin Holla Fri, 03 Apr 2020 00:00:00 +0000 diff --git a/debian/compat b/debian/compat new file mode 100644 index 000000000..ec635144f --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 000000000..e2e88573f --- /dev/null +++ b/debian/control @@ -0,0 +1,20 @@ +Source: sonic-mgmt-common +Maintainer: Sachin Holla +Build-Depends: debhelper (>= 8.0.0), + dh-systemd +Vcs-Git: https://github.com/Azure/sonic-mgmt-common +Homepage: https://github.com/Azure/SONiC/ +Standards-Version: 3.9.3 +Section: net + +Package: sonic-mgmt-common +Priority: extra +Architecture: amd64 +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: SONiC Management Infrastructure + +Package: sonic-mgmt-common-codegen +Priority: extra +Architecture: amd64 +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: SONiC Management Infrastructure generated code diff --git a/debian/rules b/debian/rules new file mode 100755 index 000000000..ad165e4fa --- /dev/null +++ b/debian/rules @@ -0,0 +1,4 @@ +#!/usr/bin/make -f +%: + dh $@ --with systemd --parallel + diff --git a/debian/sonic-mgmt-common-codegen.install b/debian/sonic-mgmt-common-codegen.install new file mode 100644 index 000000000..96a84855e --- /dev/null +++ b/debian/sonic-mgmt-common-codegen.install @@ -0,0 +1,5 @@ +translib/ocbinds/ocbinds.go sonic/src/sonic-mgmt-common/translib/ocbinds +models/yang/allyangs.tree sonic/src/sonic-mgmt-common/models/yang +models/yang/allyangs_tree.html sonic/src/sonic-mgmt-common/models/yang +models/yang/sonic_allyangs.tree sonic/src/sonic-mgmt-common/models/yang +models/yang/sonic_allyangs_tree.html sonic/src/sonic-mgmt-common/models/yang diff --git a/debian/sonic-mgmt-common.install b/debian/sonic-mgmt-common.install new file mode 100644 index 000000000..56647a0c5 --- /dev/null +++ b/debian/sonic-mgmt-common.install @@ -0,0 +1,14 @@ +# Yang models +models/yang/*.yang usr/models/yang +models/yang/common/*.yang usr/models/yang +models/yang/extensions/*.yang usr/models/yang +models/yang/sonic/*.yang usr/models/yang +models/yang/sonic/common/*.yang usr/models/yang +models/yang/annotations/*.yang usr/models/yang +config/transformer/models_list usr/models/yang +build/yang/api_ignore usr/models/yang + +# CVL files +build/cvl/schema usr/sbin +cvl/conf/cvl_cfg.json usr/sbin + diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..006bc0313 --- /dev/null +++ b/go.mod @@ -0,0 +1,21 @@ +module github.com/Azure/sonic-mgmt-common + +require ( + github.com/Workiva/go-datastructures v1.0.50 + github.com/antchfx/jsonquery v1.1.0 + github.com/antchfx/xmlquery v1.2.1 + github.com/antchfx/xpath v1.1.2 // indirect + github.com/go-redis/redis v6.15.6+incompatible + github.com/go-redis/redis/v7 v7.0.0-beta.3.0.20190824101152-d19aba07b476 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c + github.com/openconfig/goyang v0.0.0-20190924211109-064f9690516f + github.com/openconfig/ygot v0.6.1-0.20190723223108-724a6b18a922 + github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 // indirect + golang.org/x/text v0.3.0 + google.golang.org/grpc v1.25.1 // indirect +) + +go 1.13 diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..c4c44c964 --- /dev/null +++ b/go.sum @@ -0,0 +1,82 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Workiva/go-datastructures v1.0.50 h1:slDmfW6KCHcC7U+LP3DDBbm4fqTwZGn1beOFPfGaLvo= +github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= +github.com/antchfx/jsonquery v1.1.0 h1:ZeqeHheI8WsEN5blUqZXZ30w2jrbFvlQIq5B7X7Z86E= +github.com/antchfx/jsonquery v1.1.0/go.mod h1:h7950pvPrUZzJIflNqsELgDQovTpPNa0rAHf8NwjegY= +github.com/antchfx/xmlquery v1.2.1 h1:wE4xjHrqOScP440wdv23Xkg0Gr8JryW0ptqodPH+y2U= +github.com/antchfx/xmlquery v1.2.1/go.mod h1:/+CnyD/DzHRnv2eRxrVbieRU/FIF6N0C+7oTtyUtCKk= +github.com/antchfx/xpath v1.1.2 h1:YziPrtM0gEJBnhdUGxYcIVYXZ8FXbtbovxOi+UW/yWQ= +github.com/antchfx/xpath v1.1.2/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-redis/redis v6.15.6+incompatible h1:H9evprGPLI8+ci7fxQx6WNZHJSb7be8FqJQRhdQZ5Sg= +github.com/go-redis/redis v6.15.6+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v7 v7.0.0-beta.3.0.20190824101152-d19aba07b476/go.mod h1:xhhSbUMTsleRPur+Vgx9sUHtyN33bdjxY+9/0n9Ig8s= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c h1:a380JP+B7xlMbEQOlha1buKhzBPXFqgFXplyWCEIGEY= +github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc= +github.com/openconfig/goyang v0.0.0-20190924211109-064f9690516f h1:BaekRUaWpfaRBP3mShDZaNi4+EHbdli7D6YXc/TP3lo= +github.com/openconfig/goyang v0.0.0-20190924211109-064f9690516f/go.mod h1:dhXaV0JgHJzdrHi2l+w0fZrwArtXL7jEFoiqLEdmkvU= +github.com/openconfig/ygot v0.6.1-0.20190723223108-724a6b18a922 h1:zBLb75mrLMxabjsAhPk/2qxbht+BwHKFWBvRAB4Fd2U= +github.com/openconfig/ygot v0.6.1-0.20190723223108-724a6b18a922/go.mod h1:o30svNf7O0xK+R35tlx95odkDmZWS9JyWWQSmIhqwAs= +github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 h1:YtFkrqsMEj7YqpIhRteVxJxCeC3jJBieuLr0d4C4rSA= +github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/models/yang/.gitignore b/models/yang/.gitignore new file mode 100644 index 000000000..16e3dfc50 --- /dev/null +++ b/models/yang/.gitignore @@ -0,0 +1,2 @@ +*tree.html +*.tree diff --git a/models/yang/Makefile b/models/yang/Makefile new file mode 100644 index 000000000..54f9c6dd5 --- /dev/null +++ b/models/yang/Makefile @@ -0,0 +1,123 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +TOPDIR := ../.. +BUILD_DIR := $(TOPDIR)/build + +YANGDIR := . +YANGDIR_COMMON := $(YANGDIR)/common +YANGDIR_EXTENSIONS := $(YANGDIR)/extensions +YANGDIR_ANNOTATIONS := $(YANGDIR)/annotations +YANG_MOD_FILES := $(wildcard *.yang) +YANG_MOD_FILES += $(wildcard $(YANGDIR_EXTENSIONS)/*.yang) +YANG_COMMON_FILES := $(wildcard $(YANGDIR_COMMON)/*.yang) +YANG_ANNOTATION_FILES := $(wildcard $(YANGDIR_ANNOTATIONS)/*.yang) + +YANGDIR_SONIC := $(YANGDIR)/sonic +YANGDIR_SONIC_COMMON := $(YANGDIR_SONIC)/common +SONIC_YANG_MOD_FILES := $(wildcard *.yang) +SONIC_YANG_COMMON_FILES := $(wildcard $(YANGDIR_SONIC_COMMON)/*.yang) + + +YANG_BUILD_DIR := $(BUILD_DIR)/yang +YANG_API_IGNORES := $(YANG_BUILD_DIR)/api_ignore +YANG_ANNOTS_CHK := $(YANG_BUILD_DIR)/annots_checked + +TOOLS_DIR := $(TOPDIR)/tools +PYANG_PLUGIN_DIR := $(TOOLS_DIR)/pyang/pyang_plugins +PYANG ?= pyang + +ALL_TARGETS := allyangs.tree allyangs_tree.html +ALL_TARGETS += sonic_allyangs.tree sonic_allyangs_tree.html +ALL_TARGETS += $(YANG_API_IGNORES) #$(YANG_ANNOTS_CHK) + +all: $(ALL_TARGETS) + +%/.: + mkdir -p $@ + +.SECONDEXPANSION: + +#====================================================================== +# Syntax check for annotation files +#====================================================================== + +.PHONY: annot +annot: $(YANG_ANNOTS_CHK) + +$(YANG_ANNOTS_CHK): $(YANG_ANNOTATION_FILES) $(YANG_MOD_FILES) $(YANG_COMMON_FILES) | $$(@D)/. + $(PYANG) --strict -f tree \ + -p $(YANGDIR_COMMON):$(YANGDIR):$(YANGDIR_EXTENSIONS):$(YANGDIR_SONIC) \ + $(YANG_ANNOTATION_FILES) > /dev/null + touch $@ + +#====================================================================== +# Yang tree for standard yangs and their extensions +#====================================================================== + +allyangs.tree: $(YANG_MOD_FILES) $(YANG_COMMON_FILES) + $(PYANG) \ + -f tree \ + -o $@ \ + -p $(YANGDIR_COMMON):$(YANGDIR) \ + $(YANG_MOD_FILES) + @echo "+++++ Generation of YANG tree for Yang modules completed +++++" + +allyangs_tree.html: $(YANG_MOD_FILES) $(YANG_COMMON_FILES) + $(PYANG) \ + -f jstree \ + -o $@ \ + -p $(YANGDIR_COMMON):$(YANGDIR) \ + $(YANG_MOD_FILES) + @echo "+++++ Generation of HTML tree for Yang modules completed +++++" + +#====================================================================== +# Yang tree for SONiC yangs +#====================================================================== + +sonic_allyangs.tree: $(SONIC_YANG_MOD_FILES) $(SONIC_YANG_COMMON_FILES) + $(PYANG) \ + -f tree \ + -o $@ \ + -p $(YANGDIR_SONIC_COMMON):$(YANGDIR_SONIC):$(YANGDIR_COMMON) \ + $(SONIC_YANG_MOD_FILES) + @echo "+++++ Generation of YANG tree for Sonic Yang modules completed +++++" + +sonic_allyangs_tree.html: $(SONIC_YANG_MOD_FILES) $(SONIC_YANG_COMMON_FILES) + $(PYANG) \ + -f jstree \ + -o $@ \ + -p $(YANGDIR_SONIC_COMMON):$(YANGDIR_SONIC):$(YANGDIR_COMMON) \ + $(SONIC_YANG_MOD_FILES) + @echo "+++++ Generation of HTML tree for Sonic Yang modules completed +++++" + +#====================================================================== +# Generate api_ignore file +#====================================================================== +$(YANG_API_IGNORES): $(YANG_ANNOTATION_FILES) | $$(@D)/. + echo "# Yangs ignored" > $@ + (cd $(YANGDIR_ANNOTATIONS) && ls -f *.yang) >> $@ + +#====================================================================== +# Cleanups +#====================================================================== + +clean: + $(RM) $(ALL_TARGETS) + diff --git a/models/yang/annotations/openconfig-acl-annot.yang b/models/yang/annotations/openconfig-acl-annot.yang new file mode 100644 index 000000000..8129fe089 --- /dev/null +++ b/models/yang/annotations/openconfig-acl-annot.yang @@ -0,0 +1,197 @@ +module openconfig-acl-annot { + + yang-version "1"; + + namespace "http://openconfig.net/yang/annotation"; + prefix "oc-acl-annot"; + + import sonic-extensions { prefix sonic-ext; } + import openconfig-acl { prefix oc-acl; } + import openconfig-packet-match { prefix oc-pkt-match; } + + deviation /oc-acl:acl { + deviate add { + sonic-ext:post-transformer "acl_post_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set { + deviate add { + sonic-ext:table-name "ACL_TABLE"; + sonic-ext:key-transformer "acl_set_key_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:config/oc-acl:name { + deviate add { + sonic-ext:field-transformer "acl_set_name_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:config/oc-acl:type { + deviate add { + sonic-ext:field-transformer "acl_type_field_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:config/oc-acl:description { + deviate add { + sonic-ext:field-name "policy_desc"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:state/oc-acl:name { + deviate add { + sonic-ext:field-transformer "acl_set_name_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:state/oc-acl:type { + deviate add { + sonic-ext:field-transformer "acl_type_field_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:state/oc-acl:description { + deviate add { + sonic-ext:field-name "policy_desc"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry { + deviate add { + sonic-ext:table-name "ACL_RULE"; + sonic-ext:key-transformer "acl_entry_key_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-acl:ipv4 { + deviate add { + sonic-ext:get-validate "validate_ipv4"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-acl:ipv6 { + deviate add { + sonic-ext:get-validate "validate_ipv6"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-acl:config/oc-acl:sequence-id { + deviate add { + sonic-ext:field-transformer "acl_entry_sequenceid_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv4/oc-pkt-match:config/oc-pkt-match:source-address { + deviate add { + sonic-ext:field-name "SRC_IP"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv4/oc-pkt-match:config/oc-pkt-match:destination-address { + deviate add { + sonic-ext:field-name "DST_IP"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv4/oc-pkt-match:config/oc-pkt-match:protocol { + deviate add { + sonic-ext:field-transformer "acl_ip_protocol_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv4/oc-pkt-match:state/oc-pkt-match:source-address { + deviate add { + sonic-ext:field-name "SRC_IP"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv4/oc-pkt-match:state/oc-pkt-match:destination-address { + deviate add { + sonic-ext:field-name "DST_IP"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv4/oc-pkt-match:state/oc-pkt-match:protocol { + deviate add { + sonic-ext:field-transformer "acl_ip_protocol_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv6/oc-pkt-match:config/oc-pkt-match:source-address { + deviate add { + sonic-ext:field-name "SRC_IP"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv6/oc-pkt-match:config/oc-pkt-match:destination-address { + deviate add { + sonic-ext:field-name "DST_IP"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:ipv6/oc-pkt-match:config/oc-pkt-match:protocol { + deviate add { + sonic-ext:field-transformer "acl_ip_protocol_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:transport/oc-pkt-match:config/oc-pkt-match:source-port { + deviate add { + sonic-ext:field-transformer "acl_source_port_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:transport/oc-pkt-match:config/oc-pkt-match:destination-port { + deviate add { + sonic-ext:field-transformer "acl_destination_port_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:transport/oc-pkt-match:state/oc-pkt-match:source-port { + deviate add { + sonic-ext:field-transformer "acl_source_port_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:transport/oc-pkt-match:state/oc-pkt-match:destination-port { + deviate add { + sonic-ext:field-transformer "acl_destination_port_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:transport/oc-pkt-match:config/oc-pkt-match:tcp-flags { + deviate add { + sonic-ext:field-transformer "acl_tcp_flags_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-pkt-match:l2/oc-pkt-match:config/oc-pkt-match:ethertype { + deviate add { + sonic-ext:field-transformer "acl_l2_ethertype_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-acl:actions/oc-acl:config/oc-acl:forwarding-action { + deviate add { + sonic-ext:field-name "PACKET_ACTION"; + sonic-ext:field-transformer "acl_forwarding_action_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:acl-sets/oc-acl:acl-set/oc-acl:acl-entries/oc-acl:acl-entry/oc-acl:actions/oc-acl:state/oc-acl:forwarding-action { + deviate add { + sonic-ext:field-name "PACKET_ACTION"; + sonic-ext:field-transformer "acl_forwarding_action_xfmr"; + } + } + + deviation /oc-acl:acl/oc-acl:interfaces { + deviate add { + sonic-ext:subtree-transformer "acl_port_bindings_xfmr"; + } + } + +} + diff --git a/models/yang/annotations/sonic-extensions.yang b/models/yang/annotations/sonic-extensions.yang new file mode 100644 index 000000000..c1252ef60 --- /dev/null +++ b/models/yang/annotations/sonic-extensions.yang @@ -0,0 +1,88 @@ +module sonic-extensions { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/sonic-ext"; + + prefix "sonic-ext"; + + // meta + organization "Sonic working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module provides extensions to the YANG language to allow + Sonic specific functionality and meta-data to be defined."; + + revision "2019-08-30" { + description + "Add extensions for redis DB mappings to identify the Redis DB name."; + } + + revision "2019-07-26" { + description + "Add extensionis for redis DB mappings for table, table-keys, table-fields and corresponding transformer methods."; + } + + + // extension statements + extension table-name { + argument "table-name"; + description "Db table name."; + } + + extension key-transformer { + argument "key-transformer-name"; + description "Db table key transformer name indicating that the list keys together form db table keys."; + } + + extension key-delimiter { + argument "key-delimiter-string"; + description "Db table key values delimiter."; + } + + extension field-name { + argument "field-name"; + description "Db table field name."; + } + + extension openapi-opid { + argument "openapi-opid"; + description "Custom Operation ID for OpenAPI"; + } + + extension field-transformer { + argument "field-transformer-name"; + description "Db table field transformer name.This can be applied to either transform yang value to some different format + or choose a specific DB field based on the type of yang value."; + } + + extension subtree-transformer { + argument "subtree-transformer-name"; + description "Subtree/node level transformer name that will have db mappings for an entire yang subtree."; + } + + extension post-transformer { + argument "post-transformer-name"; + description "Transformer name that will perform post-translation tasks."; + } + + extension get-validate { + argument "get-validate-name"; + description "Validation callpoint used to validate a YANG node during data translation back to YANG as a response to GET."; + } + + extension db-name { + argument "db-name"; + description "DB name that will indicate where data is stored. Eg: Config DB, App DB etc"; + } + extension table-transformer { + argument "table-transformer-name"; + description "Db table transformer name.This can be applied to either transform yang value to some different format + or choose a specific DB table based on the type."; + } +} diff --git a/models/yang/common/iana-if-type.yang b/models/yang/common/iana-if-type.yang new file mode 100644 index 000000000..74e46b4b2 --- /dev/null +++ b/models/yang/common/iana-if-type.yang @@ -0,0 +1,1554 @@ +module iana-if-type { + namespace "urn:ietf:params:xml:ns:yang:iana-if-type"; + prefix ianaift; + + import ietf-interfaces { + prefix if; + } + + organization "IANA"; + contact + " Internet Assigned Numbers Authority + + Postal: ICANN + 12025 Waterfront Drive, Suite 300 + Los Angeles, CA 90094-2536 + United States + + Tel: +1 310 301 5800 + "; + description + "This YANG module defines YANG identities for IANA-registered + interface types. + + This YANG module is maintained by IANA and reflects the + 'ifType definitions' registry. + + The latest revision of this YANG module can be obtained from + the IANA web site. + + Requests for new values should be made to IANA via + email (iana&iana.org). + + Copyright (c) 2014 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + The initial version of this YANG module is part of RFC 7224; + see the RFC itself for full legal notices."; + reference + "IANA 'ifType definitions' registry. + "; + + revision 2015-06-12 { + description + "Corrected formatting issue."; + } + revision 2014-09-24 { + description + "Registered ifType 280."; + } + revision 2014-09-19 { + description + "Registered ifType 279."; + } + revision 2014-07-03 { + description + "Registered ifTypes 277-278."; + } + revision 2014-05-19 { + description + "Updated the contact address."; + } + revision 2014-05-08 { + description + "Initial revision."; + reference + "RFC 7224: IANA Interface Type YANG Module"; + } + + identity iana-interface-type { + base if:interface-type; + description + "This identity is used as a base for all interface types + defined in the 'ifType definitions' registry."; + } + + identity other { + base iana-interface-type; + } + identity regular1822 { + base iana-interface-type; + } + identity hdh1822 { + base iana-interface-type; + } + identity ddnX25 { + base iana-interface-type; + } + identity rfc877x25 { + base iana-interface-type; + reference + "RFC 1382 - SNMP MIB Extension for the X.25 Packet Layer"; + } + identity ethernetCsmacd { + base iana-interface-type; + description + "For all Ethernet-like interfaces, regardless of speed, + as per RFC 3635."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity iso88023Csmacd { + base iana-interface-type; + status deprecated; + description + "Deprecated via RFC 3635. + Use ethernetCsmacd(6) instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity iso88024TokenBus { + base iana-interface-type; + } + identity iso88025TokenRing { + base iana-interface-type; + } + identity iso88026Man { + base iana-interface-type; + } + identity starLan { + base iana-interface-type; + status deprecated; + description + "Deprecated via RFC 3635. + Use ethernetCsmacd(6) instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity proteon10Mbit { + base iana-interface-type; + } + identity proteon80Mbit { + base iana-interface-type; + } + identity hyperchannel { + base iana-interface-type; + } + identity fddi { + base iana-interface-type; + reference + "RFC 1512 - FDDI Management Information Base"; + } + identity lapb { + base iana-interface-type; + reference + "RFC 1381 - SNMP MIB Extension for X.25 LAPB"; + } + identity sdlc { + base iana-interface-type; + } + identity ds1 { + base iana-interface-type; + description + "DS1-MIB."; + reference + "RFC 4805 - Definitions of Managed Objects for the + DS1, J1, E1, DS2, and E2 Interface Types"; + } + identity e1 { + base iana-interface-type; + status obsolete; + description + "Obsolete; see DS1-MIB."; + reference + "RFC 4805 - Definitions of Managed Objects for the + DS1, J1, E1, DS2, and E2 Interface Types"; + } + identity basicISDN { + base iana-interface-type; + description + "No longer used. See also RFC 2127."; + } + identity primaryISDN { + base iana-interface-type; + description + "No longer used. See also RFC 2127."; + } + identity propPointToPointSerial { + base iana-interface-type; + description + "Proprietary serial."; + } + identity ppp { + base iana-interface-type; + } + identity softwareLoopback { + base iana-interface-type; + } + identity eon { + base iana-interface-type; + description + "CLNP over IP."; + } + identity ethernet3Mbit { + base iana-interface-type; + } + identity nsip { + base iana-interface-type; + description + "XNS over IP."; + } + identity slip { + base iana-interface-type; + description + "Generic SLIP."; + } + identity ultra { + base iana-interface-type; + description + "Ultra Technologies."; + } + identity ds3 { + base iana-interface-type; + description + "DS3-MIB."; + reference + "RFC 3896 - Definitions of Managed Objects for the + DS3/E3 Interface Type"; + } + identity sip { + base iana-interface-type; + description + "SMDS, coffee."; + reference + "RFC 1694 - Definitions of Managed Objects for SMDS + Interfaces using SMIv2"; + } + identity frameRelay { + base iana-interface-type; + description + "DTE only."; + reference + "RFC 2115 - Management Information Base for Frame Relay + DTEs Using SMIv2"; + } + identity rs232 { + base iana-interface-type; + reference + "RFC 1659 - Definitions of Managed Objects for RS-232-like + Hardware Devices using SMIv2"; + } + identity para { + base iana-interface-type; + description + "Parallel-port."; + reference + "RFC 1660 - Definitions of Managed Objects for + Parallel-printer-like Hardware Devices using + SMIv2"; + } + identity arcnet { + base iana-interface-type; + description + "ARCnet."; + } + identity arcnetPlus { + base iana-interface-type; + description + "ARCnet Plus."; + } + identity atm { + base iana-interface-type; + description + "ATM cells."; + } + identity miox25 { + base iana-interface-type; + reference + "RFC 1461 - SNMP MIB extension for Multiprotocol + Interconnect over X.25"; + } + identity sonet { + base iana-interface-type; + description + "SONET or SDH."; + } + identity x25ple { + base iana-interface-type; + reference + "RFC 2127 - ISDN Management Information Base using SMIv2"; + } + identity iso88022llc { + base iana-interface-type; + } + identity localTalk { + base iana-interface-type; + } + identity smdsDxi { + base iana-interface-type; + } + identity frameRelayService { + base iana-interface-type; + description + "FRNETSERV-MIB."; + reference + "RFC 2954 - Definitions of Managed Objects for Frame + Relay Service"; + } + identity v35 { + base iana-interface-type; + } + identity hssi { + base iana-interface-type; + } + identity hippi { + base iana-interface-type; + } + identity modem { + base iana-interface-type; + description + "Generic modem."; + } + identity aal5 { + base iana-interface-type; + description + "AAL5 over ATM."; + } + identity sonetPath { + base iana-interface-type; + } + identity sonetVT { + base iana-interface-type; + } + identity smdsIcip { + base iana-interface-type; + description + "SMDS InterCarrier Interface."; + } + identity propVirtual { + base iana-interface-type; + description + "Proprietary virtual/internal."; + reference + "RFC 2863 - The Interfaces Group MIB"; + } + identity propMultiplexor { + base iana-interface-type; + description + "Proprietary multiplexing."; + reference + "RFC 2863 - The Interfaces Group MIB"; + } + identity ieee80212 { + base iana-interface-type; + description + "100BaseVG."; + } + identity fibreChannel { + base iana-interface-type; + description + "Fibre Channel."; + } + identity hippiInterface { + base iana-interface-type; + description + "HIPPI interfaces."; + } + identity frameRelayInterconnect { + base iana-interface-type; + status obsolete; + description + "Obsolete; use either + frameRelay(32) or frameRelayService(44)."; + } + identity aflane8023 { + base iana-interface-type; + description + "ATM Emulated LAN for 802.3."; + } + identity aflane8025 { + base iana-interface-type; + description + "ATM Emulated LAN for 802.5."; + } + identity cctEmul { + base iana-interface-type; + description + "ATM Emulated circuit."; + } + identity fastEther { + base iana-interface-type; + status deprecated; + description + "Obsoleted via RFC 3635. + ethernetCsmacd(6) should be used instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity isdn { + base iana-interface-type; + description + "ISDN and X.25."; + reference + "RFC 1356 - Multiprotocol Interconnect on X.25 and ISDN + in the Packet Mode"; + } + identity v11 { + base iana-interface-type; + description + "CCITT V.11/X.21."; + } + identity v36 { + base iana-interface-type; + description + "CCITT V.36."; + } + identity g703at64k { + base iana-interface-type; + description + "CCITT G703 at 64Kbps."; + } + identity g703at2mb { + base iana-interface-type; + status obsolete; + description + "Obsolete; see DS1-MIB."; + } + identity qllc { + base iana-interface-type; + description + "SNA QLLC."; + } + identity fastEtherFX { + base iana-interface-type; + status deprecated; + description + "Obsoleted via RFC 3635. + ethernetCsmacd(6) should be used instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity channel { + base iana-interface-type; + description + "Channel."; + } + identity ieee80211 { + base iana-interface-type; + description + "Radio spread spectrum."; + } + identity ibm370parChan { + base iana-interface-type; + description + "IBM System 360/370 OEMI Channel."; + } + identity escon { + base iana-interface-type; + description + "IBM Enterprise Systems Connection."; + } + identity dlsw { + base iana-interface-type; + description + "Data Link Switching."; + } + identity isdns { + base iana-interface-type; + description + "ISDN S/T interface."; + } + identity isdnu { + base iana-interface-type; + description + "ISDN U interface."; + } + identity lapd { + base iana-interface-type; + description + "Link Access Protocol D."; + } + identity ipSwitch { + base iana-interface-type; + description + "IP Switching Objects."; + } + identity rsrb { + base iana-interface-type; + description + "Remote Source Route Bridging."; + } + identity atmLogical { + base iana-interface-type; + description + "ATM Logical Port."; + reference + "RFC 3606 - Definitions of Supplemental Managed Objects + for ATM Interface"; + } + identity ds0 { + base iana-interface-type; + description + "Digital Signal Level 0."; + reference + "RFC 2494 - Definitions of Managed Objects for the DS0 + and DS0 Bundle Interface Type"; + } + identity ds0Bundle { + base iana-interface-type; + description + "Group of ds0s on the same ds1."; + reference + "RFC 2494 - Definitions of Managed Objects for the DS0 + and DS0 Bundle Interface Type"; + } + identity bsc { + base iana-interface-type; + description + "Bisynchronous Protocol."; + } + identity async { + base iana-interface-type; + description + "Asynchronous Protocol."; + } + identity cnr { + base iana-interface-type; + description + "Combat Net Radio."; + } + identity iso88025Dtr { + base iana-interface-type; + description + "ISO 802.5r DTR."; + } + identity eplrs { + base iana-interface-type; + description + "Ext Pos Loc Report Sys."; + } + identity arap { + base iana-interface-type; + description + "Appletalk Remote Access Protocol."; + } + identity propCnls { + base iana-interface-type; + description + "Proprietary Connectionless Protocol."; + } + identity hostPad { + base iana-interface-type; + description + "CCITT-ITU X.29 PAD Protocol."; + } + identity termPad { + base iana-interface-type; + description + "CCITT-ITU X.3 PAD Facility."; + } + identity frameRelayMPI { + base iana-interface-type; + description + "Multiproto Interconnect over FR."; + } + identity x213 { + base iana-interface-type; + description + "CCITT-ITU X213."; + } + identity adsl { + base iana-interface-type; + description + "Asymmetric Digital Subscriber Loop."; + } + identity radsl { + base iana-interface-type; + description + "Rate-Adapt. Digital Subscriber Loop."; + } + identity sdsl { + base iana-interface-type; + description + "Symmetric Digital Subscriber Loop."; + } + identity vdsl { + base iana-interface-type; + description + "Very H-Speed Digital Subscrib. Loop."; + } + identity iso88025CRFPInt { + base iana-interface-type; + description + "ISO 802.5 CRFP."; + } + identity myrinet { + base iana-interface-type; + description + "Myricom Myrinet."; + } + identity voiceEM { + base iana-interface-type; + description + "Voice recEive and transMit."; + } + identity voiceFXO { + base iana-interface-type; + description + "Voice Foreign Exchange Office."; + } + identity voiceFXS { + base iana-interface-type; + description + "Voice Foreign Exchange Station."; + } + identity voiceEncap { + base iana-interface-type; + description + "Voice encapsulation."; + } + identity voiceOverIp { + base iana-interface-type; + description + "Voice over IP encapsulation."; + } + identity atmDxi { + base iana-interface-type; + description + "ATM DXI."; + } + identity atmFuni { + base iana-interface-type; + description + "ATM FUNI."; + } + identity atmIma { + base iana-interface-type; + description + "ATM IMA."; + } + identity pppMultilinkBundle { + base iana-interface-type; + description + "PPP Multilink Bundle."; + } + identity ipOverCdlc { + base iana-interface-type; + description + "IBM ipOverCdlc."; + } + identity ipOverClaw { + base iana-interface-type; + description + "IBM Common Link Access to Workstn."; + } + identity stackToStack { + base iana-interface-type; + description + "IBM stackToStack."; + } + identity virtualIpAddress { + base iana-interface-type; + description + "IBM VIPA."; + } + identity mpc { + base iana-interface-type; + description + "IBM multi-protocol channel support."; + } + identity ipOverAtm { + base iana-interface-type; + description + "IBM ipOverAtm."; + reference + "RFC 2320 - Definitions of Managed Objects for Classical IP + and ARP Over ATM Using SMIv2 (IPOA-MIB)"; + } + identity iso88025Fiber { + base iana-interface-type; + description + "ISO 802.5j Fiber Token Ring."; + } + identity tdlc { + base iana-interface-type; + description + "IBM twinaxial data link control."; + } + identity gigabitEthernet { + base iana-interface-type; + status deprecated; + description + "Obsoleted via RFC 3635. + ethernetCsmacd(6) should be used instead."; + reference + "RFC 3635 - Definitions of Managed Objects for the + Ethernet-like Interface Types"; + } + identity hdlc { + base iana-interface-type; + description + "HDLC."; + } + identity lapf { + base iana-interface-type; + description + "LAP F."; + } + identity v37 { + base iana-interface-type; + description + "V.37."; + } + identity x25mlp { + base iana-interface-type; + description + "Multi-Link Protocol."; + } + identity x25huntGroup { + base iana-interface-type; + description + "X25 Hunt Group."; + } + identity transpHdlc { + base iana-interface-type; + description + "Transp HDLC."; + } + identity interleave { + base iana-interface-type; + description + "Interleave channel."; + } + identity fast { + base iana-interface-type; + description + "Fast channel."; + } + identity ip { + base iana-interface-type; + description + "IP (for APPN HPR in IP networks)."; + } + identity docsCableMaclayer { + base iana-interface-type; + description + "CATV Mac Layer."; + } + identity docsCableDownstream { + base iana-interface-type; + description + "CATV Downstream interface."; + } + identity docsCableUpstream { + base iana-interface-type; + description + "CATV Upstream interface."; + } + identity a12MppSwitch { + base iana-interface-type; + description + "Avalon Parallel Processor."; + } + identity tunnel { + base iana-interface-type; + description + "Encapsulation interface."; + } + identity coffee { + base iana-interface-type; + description + "Coffee pot."; + reference + "RFC 2325 - Coffee MIB"; + } + identity ces { + base iana-interface-type; + description + "Circuit Emulation Service."; + } + identity atmSubInterface { + base iana-interface-type; + description + "ATM Sub Interface."; + } + identity l2vlan { + base iana-interface-type; + description + "Layer 2 Virtual LAN using 802.1Q."; + } + identity l3ipvlan { + base iana-interface-type; + description + "Layer 3 Virtual LAN using IP."; + } + identity l3ipxvlan { + base iana-interface-type; + description + "Layer 3 Virtual LAN using IPX."; + } + identity digitalPowerline { + base iana-interface-type; + description + "IP over Power Lines."; + } + identity mediaMailOverIp { + base iana-interface-type; + description + "Multimedia Mail over IP."; + } + identity dtm { + base iana-interface-type; + description + "Dynamic synchronous Transfer Mode."; + } + identity dcn { + base iana-interface-type; + description + "Data Communications Network."; + } + identity ipForward { + base iana-interface-type; + description + "IP Forwarding Interface."; + } + identity msdsl { + base iana-interface-type; + description + "Multi-rate Symmetric DSL."; + } + identity ieee1394 { + base iana-interface-type; + + description + "IEEE1394 High Performance Serial Bus."; + } + identity if-gsn { + base iana-interface-type; + description + "HIPPI-6400."; + } + identity dvbRccMacLayer { + base iana-interface-type; + description + "DVB-RCC MAC Layer."; + } + identity dvbRccDownstream { + base iana-interface-type; + description + "DVB-RCC Downstream Channel."; + } + identity dvbRccUpstream { + base iana-interface-type; + description + "DVB-RCC Upstream Channel."; + } + identity atmVirtual { + base iana-interface-type; + description + "ATM Virtual Interface."; + } + identity mplsTunnel { + base iana-interface-type; + description + "MPLS Tunnel Virtual Interface."; + } + identity srp { + base iana-interface-type; + description + "Spatial Reuse Protocol."; + } + identity voiceOverAtm { + base iana-interface-type; + description + "Voice over ATM."; + } + identity voiceOverFrameRelay { + base iana-interface-type; + description + "Voice Over Frame Relay."; + } + identity idsl { + base iana-interface-type; + description + "Digital Subscriber Loop over ISDN."; + } + identity compositeLink { + base iana-interface-type; + description + "Avici Composite Link Interface."; + } + identity ss7SigLink { + base iana-interface-type; + description + "SS7 Signaling Link."; + } + identity propWirelessP2P { + base iana-interface-type; + description + "Prop. P2P wireless interface."; + } + identity frForward { + base iana-interface-type; + description + "Frame Forward Interface."; + } + identity rfc1483 { + base iana-interface-type; + description + "Multiprotocol over ATM AAL5."; + reference + "RFC 1483 - Multiprotocol Encapsulation over ATM + Adaptation Layer 5"; + } + identity usb { + base iana-interface-type; + description + "USB Interface."; + } + identity ieee8023adLag { + base iana-interface-type; + description + "IEEE 802.3ad Link Aggregate."; + } + identity bgppolicyaccounting { + base iana-interface-type; + description + "BGP Policy Accounting."; + } + identity frf16MfrBundle { + base iana-interface-type; + description + "FRF.16 Multilink Frame Relay."; + } + identity h323Gatekeeper { + base iana-interface-type; + description + "H323 Gatekeeper."; + } + identity h323Proxy { + base iana-interface-type; + description + "H323 Voice and Video Proxy."; + } + identity mpls { + base iana-interface-type; + description + "MPLS."; + } + identity mfSigLink { + base iana-interface-type; + description + "Multi-frequency signaling link."; + } + identity hdsl2 { + base iana-interface-type; + description + "High Bit-Rate DSL - 2nd generation."; + } + identity shdsl { + base iana-interface-type; + description + "Multirate HDSL2."; + } + identity ds1FDL { + base iana-interface-type; + description + "Facility Data Link (4Kbps) on a DS1."; + } + identity pos { + base iana-interface-type; + description + "Packet over SONET/SDH Interface."; + } + identity dvbAsiIn { + base iana-interface-type; + description + "DVB-ASI Input."; + } + identity dvbAsiOut { + base iana-interface-type; + description + "DVB-ASI Output."; + } + identity plc { + base iana-interface-type; + description + "Power Line Communications."; + } + identity nfas { + base iana-interface-type; + description + "Non-Facility Associated Signaling."; + } + identity tr008 { + base iana-interface-type; + description + "TR008."; + } + identity gr303RDT { + base iana-interface-type; + description + "Remote Digital Terminal."; + } + identity gr303IDT { + base iana-interface-type; + description + "Integrated Digital Terminal."; + } + identity isup { + base iana-interface-type; + description + "ISUP."; + } + identity propDocsWirelessMaclayer { + base iana-interface-type; + description + "Cisco proprietary Maclayer."; + } + identity propDocsWirelessDownstream { + base iana-interface-type; + description + "Cisco proprietary Downstream."; + } + identity propDocsWirelessUpstream { + base iana-interface-type; + description + "Cisco proprietary Upstream."; + } + identity hiperlan2 { + base iana-interface-type; + description + "HIPERLAN Type 2 Radio Interface."; + } + identity propBWAp2Mp { + base iana-interface-type; + description + "PropBroadbandWirelessAccesspt2Multipt (use of this value + for IEEE 802.16 WMAN interfaces as per IEEE Std 802.16f + is deprecated, and ieee80216WMAN(237) should be used + instead)."; + } + identity sonetOverheadChannel { + base iana-interface-type; + description + "SONET Overhead Channel."; + } + identity digitalWrapperOverheadChannel { + base iana-interface-type; + description + "Digital Wrapper."; + } + identity aal2 { + base iana-interface-type; + description + "ATM adaptation layer 2."; + } + identity radioMAC { + base iana-interface-type; + description + "MAC layer over radio links."; + } + identity atmRadio { + base iana-interface-type; + description + "ATM over radio links."; + } + identity imt { + base iana-interface-type; + description + "Inter-Machine Trunks."; + } + identity mvl { + base iana-interface-type; + description + "Multiple Virtual Lines DSL."; + } + identity reachDSL { + base iana-interface-type; + description + "Long Reach DSL."; + } + identity frDlciEndPt { + base iana-interface-type; + description + "Frame Relay DLCI End Point."; + } + identity atmVciEndPt { + base iana-interface-type; + description + "ATM VCI End Point."; + } + identity opticalChannel { + base iana-interface-type; + description + "Optical Channel."; + } + identity opticalTransport { + base iana-interface-type; + description + "Optical Transport."; + } + identity propAtm { + base iana-interface-type; + description + "Proprietary ATM."; + } + identity voiceOverCable { + base iana-interface-type; + description + "Voice Over Cable Interface."; + } + identity infiniband { + base iana-interface-type; + description + "Infiniband."; + } + identity teLink { + base iana-interface-type; + description + "TE Link."; + } + identity q2931 { + base iana-interface-type; + description + "Q.2931."; + } + identity virtualTg { + base iana-interface-type; + description + "Virtual Trunk Group."; + } + identity sipTg { + base iana-interface-type; + description + "SIP Trunk Group."; + } + identity sipSig { + base iana-interface-type; + description + "SIP Signaling."; + } + identity docsCableUpstreamChannel { + base iana-interface-type; + description + "CATV Upstream Channel."; + } + identity econet { + base iana-interface-type; + description + "Acorn Econet."; + } + identity pon155 { + base iana-interface-type; + description + "FSAN 155Mb Symetrical PON interface."; + } + identity pon622 { + base iana-interface-type; + description + "FSAN 622Mb Symetrical PON interface."; + } + identity bridge { + base iana-interface-type; + description + "Transparent bridge interface."; + } + identity linegroup { + base iana-interface-type; + description + "Interface common to multiple lines."; + } + identity voiceEMFGD { + base iana-interface-type; + description + "Voice E&M Feature Group D."; + } + identity voiceFGDEANA { + base iana-interface-type; + description + "Voice FGD Exchange Access North American."; + } + identity voiceDID { + base iana-interface-type; + description + "Voice Direct Inward Dialing."; + } + identity mpegTransport { + base iana-interface-type; + description + "MPEG transport interface."; + } + identity sixToFour { + base iana-interface-type; + status deprecated; + description + "6to4 interface (DEPRECATED)."; + reference + "RFC 4087 - IP Tunnel MIB"; + } + identity gtp { + base iana-interface-type; + description + "GTP (GPRS Tunneling Protocol)."; + } + identity pdnEtherLoop1 { + base iana-interface-type; + description + "Paradyne EtherLoop 1."; + } + identity pdnEtherLoop2 { + base iana-interface-type; + description + "Paradyne EtherLoop 2."; + } + identity opticalChannelGroup { + base iana-interface-type; + description + "Optical Channel Group."; + } + identity homepna { + base iana-interface-type; + description + "HomePNA ITU-T G.989."; + } + identity gfp { + base iana-interface-type; + description + "Generic Framing Procedure (GFP)."; + } + identity ciscoISLvlan { + base iana-interface-type; + description + "Layer 2 Virtual LAN using Cisco ISL."; + } + identity actelisMetaLOOP { + base iana-interface-type; + description + "Acteleis proprietary MetaLOOP High Speed Link."; + } + identity fcipLink { + base iana-interface-type; + description + "FCIP Link."; + } + identity rpr { + base iana-interface-type; + description + "Resilient Packet Ring Interface Type."; + } + identity qam { + base iana-interface-type; + description + "RF Qam Interface."; + } + identity lmp { + base iana-interface-type; + description + "Link Management Protocol."; + reference + "RFC 4327 - Link Management Protocol (LMP) Management + Information Base (MIB)"; + } + identity cblVectaStar { + base iana-interface-type; + description + "Cambridge Broadband Networks Limited VectaStar."; + } + identity docsCableMCmtsDownstream { + base iana-interface-type; + description + "CATV Modular CMTS Downstream Interface."; + } + identity adsl2 { + base iana-interface-type; + status deprecated; + description + "Asymmetric Digital Subscriber Loop Version 2 + (DEPRECATED/OBSOLETED - please use adsl2plus(238) + instead)."; + reference + "RFC 4706 - Definitions of Managed Objects for Asymmetric + Digital Subscriber Line 2 (ADSL2)"; + } + identity macSecControlledIF { + base iana-interface-type; + description + "MACSecControlled."; + } + identity macSecUncontrolledIF { + base iana-interface-type; + description + "MACSecUncontrolled."; + } + identity aviciOpticalEther { + base iana-interface-type; + description + "Avici Optical Ethernet Aggregate."; + } + identity atmbond { + base iana-interface-type; + description + "atmbond."; + } + identity voiceFGDOS { + base iana-interface-type; + description + "Voice FGD Operator Services."; + } + identity mocaVersion1 { + base iana-interface-type; + description + "MultiMedia over Coax Alliance (MoCA) Interface + as documented in information provided privately to IANA."; + } + identity ieee80216WMAN { + base iana-interface-type; + description + "IEEE 802.16 WMAN interface."; + } + identity adsl2plus { + base iana-interface-type; + description + "Asymmetric Digital Subscriber Loop Version 2 - + Version 2 Plus and all variants."; + } + identity dvbRcsMacLayer { + base iana-interface-type; + description + "DVB-RCS MAC Layer."; + reference + "RFC 5728 - The SatLabs Group DVB-RCS MIB"; + } + identity dvbTdm { + base iana-interface-type; + description + "DVB Satellite TDM."; + reference + "RFC 5728 - The SatLabs Group DVB-RCS MIB"; + } + identity dvbRcsTdma { + base iana-interface-type; + description + "DVB-RCS TDMA."; + reference + "RFC 5728 - The SatLabs Group DVB-RCS MIB"; + } + identity x86Laps { + base iana-interface-type; + description + "LAPS based on ITU-T X.86/Y.1323."; + } + identity wwanPP { + base iana-interface-type; + description + "3GPP WWAN."; + } + identity wwanPP2 { + base iana-interface-type; + description + "3GPP2 WWAN."; + } + identity voiceEBS { + base iana-interface-type; + description + "Voice P-phone EBS physical interface."; + } + identity ifPwType { + base iana-interface-type; + description + "Pseudowire interface type."; + reference + "RFC 5601 - Pseudowire (PW) Management Information Base (MIB)"; + } + identity ilan { + base iana-interface-type; + description + "Internal LAN on a bridge per IEEE 802.1ap."; + } + identity pip { + base iana-interface-type; + description + "Provider Instance Port on a bridge per IEEE 802.1ah PBB."; + } + identity aluELP { + base iana-interface-type; + description + "Alcatel-Lucent Ethernet Link Protection."; + } + identity gpon { + base iana-interface-type; + description + "Gigabit-capable passive optical networks (G-PON) as per + ITU-T G.948."; + } + identity vdsl2 { + base iana-interface-type; + description + "Very high speed digital subscriber line Version 2 + (as per ITU-T Recommendation G.993.2)."; + reference + "RFC 5650 - Definitions of Managed Objects for Very High + Speed Digital Subscriber Line 2 (VDSL2)"; + } + identity capwapDot11Profile { + base iana-interface-type; + description + "WLAN Profile Interface."; + reference + "RFC 5834 - Control and Provisioning of Wireless Access + Points (CAPWAP) Protocol Binding MIB for + IEEE 802.11"; + } + identity capwapDot11Bss { + base iana-interface-type; + description + "WLAN BSS Interface."; + reference + "RFC 5834 - Control and Provisioning of Wireless Access + Points (CAPWAP) Protocol Binding MIB for + IEEE 802.11"; + } + identity capwapWtpVirtualRadio { + base iana-interface-type; + description + "WTP Virtual Radio Interface."; + reference + "RFC 5833 - Control and Provisioning of Wireless Access + Points (CAPWAP) Protocol Base MIB"; + } + identity bits { + base iana-interface-type; + description + "bitsport."; + } + identity docsCableUpstreamRfPort { + base iana-interface-type; + description + "DOCSIS CATV Upstream RF Port."; + } + identity cableDownstreamRfPort { + base iana-interface-type; + description + "CATV downstream RF Port."; + } + identity vmwareVirtualNic { + base iana-interface-type; + description + "VMware Virtual Network Interface."; + } + identity ieee802154 { + base iana-interface-type; + description + "IEEE 802.15.4 WPAN interface."; + reference + "IEEE 802.15.4-2006"; + } + identity otnOdu { + base iana-interface-type; + description + "OTN Optical Data Unit."; + } + identity otnOtu { + base iana-interface-type; + description + "OTN Optical channel Transport Unit."; + } + identity ifVfiType { + base iana-interface-type; + description + "VPLS Forwarding Instance Interface Type."; + } + identity g9981 { + base iana-interface-type; + description + "G.998.1 bonded interface."; + } + identity g9982 { + base iana-interface-type; + description + "G.998.2 bonded interface."; + } + identity g9983 { + base iana-interface-type; + description + "G.998.3 bonded interface."; + } + + identity aluEpon { + base iana-interface-type; + description + "Ethernet Passive Optical Networks (E-PON)."; + } + identity aluEponOnu { + base iana-interface-type; + description + "EPON Optical Network Unit."; + } + identity aluEponPhysicalUni { + base iana-interface-type; + description + "EPON physical User to Network interface."; + } + identity aluEponLogicalLink { + base iana-interface-type; + description + "The emulation of a point-to-point link over the EPON + layer."; + } + identity aluGponOnu { + base iana-interface-type; + description + "GPON Optical Network Unit."; + reference + "ITU-T G.984.2"; + } + identity aluGponPhysicalUni { + base iana-interface-type; + description + "GPON physical User to Network interface."; + reference + "ITU-T G.984.2"; + } + identity vmwareNicTeam { + base iana-interface-type; + description + "VMware NIC Team."; + } + identity docsOfdmDownstream { + base iana-interface-type; + description + "CATV Downstream OFDM interface."; + } + identity docsOfdmaUpstream { + base iana-interface-type; + description + "CATV Upstream OFDMA interface."; + } + identity gfast { + base iana-interface-type; + description + "G.fast port."; + reference + "ITU-T G.9701"; + } + identity sdci { + base iana-interface-type; + description + "SDCI (IO-Link)."; + reference + "IEC 61131-9 Edition 1.0 2013-09"; + } +} diff --git a/models/yang/common/ietf-inet-types.yang b/models/yang/common/ietf-inet-types.yang new file mode 100644 index 000000000..2f14270de --- /dev/null +++ b/models/yang/common/ietf-inet-types.yang @@ -0,0 +1,457 @@ +module ietf-inet-types { + + namespace "urn:ietf:params:xml:ns:yang:ietf-inet-types"; + prefix "inet"; + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: David Kessens + + + WG Chair: Juergen Schoenwaelder + + + Editor: Juergen Schoenwaelder + "; + + description + "This module contains a collection of generally useful derived + YANG data types for Internet addresses and related things. + + Copyright (c) 2013 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6991; see + the RFC itself for full legal notices."; + + revision 2013-07-15 { + description + "This revision adds the following new data types: + - ip-address-no-zone + - ipv4-address-no-zone + - ipv6-address-no-zone"; + reference + "RFC 6991: Common YANG Data Types"; + } + + revision 2010-09-24 { + description + "Initial revision."; + reference + "RFC 6021: Common YANG Data Types"; + } + + /*** collection of types related to protocol fields ***/ + + typedef ip-version { + type enumeration { + enum unknown { + value "0"; + description + "An unknown or unspecified version of the Internet + protocol."; + } + enum ipv4 { + value "1"; + description + "The IPv4 protocol as defined in RFC 791."; + } + enum ipv6 { + value "2"; + description + "The IPv6 protocol as defined in RFC 2460."; + } + } + description + "This value represents the version of the IP protocol. + + In the value set and its semantics, this type is equivalent + to the InetVersion textual convention of the SMIv2."; + reference + "RFC 791: Internet Protocol + RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + typedef dscp { + type uint8 { + range "0..63"; + } + description + "The dscp type represents a Differentiated Services Code Point + that may be used for marking packets in a traffic stream. + In the value set and its semantics, this type is equivalent + to the Dscp textual convention of the SMIv2."; + reference + "RFC 3289: Management Information Base for the Differentiated + Services Architecture + RFC 2474: Definition of the Differentiated Services Field + (DS Field) in the IPv4 and IPv6 Headers + RFC 2780: IANA Allocation Guidelines For Values In + the Internet Protocol and Related Headers"; + } + + typedef ipv6-flow-label { + type uint32 { + range "0..1048575"; + } + description + "The ipv6-flow-label type represents the flow identifier or Flow + Label in an IPv6 packet header that may be used to + discriminate traffic flows. + + In the value set and its semantics, this type is equivalent + to the IPv6FlowLabel textual convention of the SMIv2."; + reference + "RFC 3595: Textual Conventions for IPv6 Flow Label + RFC 2460: Internet Protocol, Version 6 (IPv6) Specification"; + } + + typedef port-number { + type uint16 { + range "0..65535"; + } + description + "The port-number type represents a 16-bit port number of an + Internet transport-layer protocol such as UDP, TCP, DCCP, or + SCTP. Port numbers are assigned by IANA. A current list of + all assignments is available from . + + Note that the port number value zero is reserved by IANA. In + situations where the value zero does not make sense, it can + be excluded by subtyping the port-number type. + In the value set and its semantics, this type is equivalent + to the InetPortNumber textual convention of the SMIv2."; + reference + "RFC 768: User Datagram Protocol + RFC 793: Transmission Control Protocol + RFC 4960: Stream Control Transmission Protocol + RFC 4340: Datagram Congestion Control Protocol (DCCP) + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + /*** collection of types related to autonomous systems ***/ + + typedef as-number { + type uint32; + description + "The as-number type represents autonomous system numbers + which identify an Autonomous System (AS). An AS is a set + of routers under a single technical administration, using + an interior gateway protocol and common metrics to route + packets within the AS, and using an exterior gateway + protocol to route packets to other ASes. IANA maintains + the AS number space and has delegated large parts to the + regional registries. + + Autonomous system numbers were originally limited to 16 + bits. BGP extensions have enlarged the autonomous system + number space to 32 bits. This type therefore uses an uint32 + base type without a range restriction in order to support + a larger autonomous system number space. + + In the value set and its semantics, this type is equivalent + to the InetAutonomousSystemNumber textual convention of + the SMIv2."; + reference + "RFC 1930: Guidelines for creation, selection, and registration + of an Autonomous System (AS) + RFC 4271: A Border Gateway Protocol 4 (BGP-4) + RFC 4001: Textual Conventions for Internet Network Addresses + RFC 6793: BGP Support for Four-Octet Autonomous System (AS) + Number Space"; + } + + /*** collection of types related to IP addresses and hostnames ***/ + + typedef ip-address { + type union { + type inet:ipv4-address; + type inet:ipv6-address; + } + description + "The ip-address type represents an IP address and is IP + version neutral. The format of the textual representation + implies the IP version. This type supports scoped addresses + by allowing zone identifiers in the address format."; + reference + "RFC 4007: IPv6 Scoped Address Architecture"; + } + + typedef ipv4-address { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '(%[\p{N}\p{L}]+)?'; + } + description + "The ipv4-address type represents an IPv4 address in + dotted-quad notation. The IPv4 address may include a zone + index, separated by a % sign. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format for the zone index is the numerical + format"; + } + + typedef ipv6-address { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(%[\p{N}\p{L}]+)?'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(%.+)?'; + } + description + "The ipv6-address type represents an IPv6 address in full, + mixed, shortened, and shortened-mixed notation. The IPv6 + address may include a zone index, separated by a % sign. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format of IPv6 addresses uses the textual + representation defined in Section 4 of RFC 5952. The + canonical format for the zone index is the numerical + format as described in Section 11.2 of RFC 4007."; + reference + "RFC 4291: IP Version 6 Addressing Architecture + RFC 4007: IPv6 Scoped Address Architecture + RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + typedef ip-address-no-zone { + type union { + type inet:ipv4-address-no-zone; + type inet:ipv6-address-no-zone; + } + description + "The ip-address-no-zone type represents an IP address and is + IP version neutral. The format of the textual representation + implies the IP version. This type does not support scoped + addresses since it does not allow zone identifiers in the + address format."; + reference + "RFC 4007: IPv6 Scoped Address Architecture"; + } + + typedef ipv4-address-no-zone { + type inet:ipv4-address { + pattern '[0-9\.]*'; + } + description + "An IPv4 address without a zone index. This type, derived from + ipv4-address, may be used in situations where the zone is + known from the context and hence no zone index is needed."; + } + + typedef ipv6-address-no-zone { + type inet:ipv6-address { + pattern '[0-9a-fA-F:\.]*'; + } + description + "An IPv6 address without a zone index. This type, derived from + ipv6-address, may be used in situations where the zone is + known from the context and hence no zone index is needed."; + reference + "RFC 4291: IP Version 6 Addressing Architecture + RFC 4007: IPv6 Scoped Address Architecture + RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + typedef ip-prefix { + type union { + type inet:ipv4-prefix; + type inet:ipv6-prefix; + } + description + "The ip-prefix type represents an IP prefix and is IP + version neutral. The format of the textual representations + implies the IP version."; + } + + typedef ipv4-prefix { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '/(([0-9])|([1-2][0-9])|(3[0-2]))'; + } + description + "The ipv4-prefix type represents an IPv4 address prefix. + The prefix length is given by the number following the + slash character and must be less than or equal to 32. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The canonical format of an IPv4 prefix has all bits of + the IPv4 address set to zero that are not part of the + IPv4 prefix."; + } + + typedef ipv6-prefix { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(/(([0-9])|([0-9]{2})|(1[0-1][0-9])|(12[0-8])))'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(/.+)'; + } + description + "The ipv6-prefix type represents an IPv6 address prefix. + The prefix length is given by the number following the + slash character and must be less than or equal to 128. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The IPv6 address should have all bits that do not belong + to the prefix set to zero. + + The canonical format of an IPv6 prefix has all bits of + the IPv6 address set to zero that are not part of the + IPv6 prefix. Furthermore, the IPv6 address is represented + as defined in Section 4 of RFC 5952."; + reference + "RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + /*** collection of domain name and URI types ***/ + + typedef domain-name { + type string { + length "1..253"; + pattern + '((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*' + + '([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)' + + '|\.'; + } + description + "The domain-name type represents a DNS domain name. The + name SHOULD be fully qualified whenever possible. + + Internet domain names are only loosely specified. Section + 3.5 of RFC 1034 recommends a syntax (modified in Section + 2.1 of RFC 1123). The pattern above is intended to allow + for current practice in domain name use, and some possible + future expansion. It is designed to hold various types of + domain names, including names used for A or AAAA records + (host names) and other records, such as SRV records. Note + that Internet host names have a stricter syntax (described + in RFC 952) than the DNS recommendations in RFCs 1034 and + 1123, and that systems that want to store host names in + schema nodes using the domain-name type are recommended to + adhere to this stricter standard to ensure interoperability. + + The encoding of DNS names in the DNS protocol is limited + to 255 characters. Since the encoding consists of labels + prefixed by a length bytes and there is a trailing NULL + byte, only 253 characters can appear in the textual dotted + notation. + + The description clause of schema nodes using the domain-name + type MUST describe when and how these names are resolved to + IP addresses. Note that the resolution of a domain-name value + may require to query multiple DNS records (e.g., A for IPv4 + and AAAA for IPv6). The order of the resolution process and + which DNS record takes precedence can either be defined + explicitly or may depend on the configuration of the + resolver. + + Domain-name values use the US-ASCII encoding. Their canonical + format uses lowercase US-ASCII characters. Internationalized + domain names MUST be A-labels as per RFC 5890."; + reference + "RFC 952: DoD Internet Host Table Specification + RFC 1034: Domain Names - Concepts and Facilities + RFC 1123: Requirements for Internet Hosts -- Application + and Support + RFC 2782: A DNS RR for specifying the location of services + (DNS SRV) + RFC 5890: Internationalized Domain Names in Applications + (IDNA): Definitions and Document Framework"; + } + + typedef host { + type union { + type inet:ip-address; + type inet:domain-name; + } + description + "The host type represents either an IP address or a DNS + domain name."; + } + + typedef uri { + type string; + description + "The uri type represents a Uniform Resource Identifier + (URI) as defined by STD 66. + + Objects using the uri type MUST be in US-ASCII encoding, + and MUST be normalized as described by RFC 3986 Sections + 6.2.1, 6.2.2.1, and 6.2.2.2. All unnecessary + percent-encoding is removed, and all case-insensitive + characters are set to lowercase except for hexadecimal + digits, which are normalized to uppercase as described in + Section 6.2.2.1. + + The purpose of this normalization is to help provide + unique URIs. Note that this normalization is not + sufficient to provide uniqueness. Two URIs that are + textually distinct after this normalization may still be + equivalent. + + Objects using the uri type may restrict the schemes that + they permit. For example, 'data:' and 'urn:' schemes + might not be appropriate. + + A zero-length URI is not a valid URI. This can be used to + express 'URI absent' where required. + + In the value set and its semantics, this type is equivalent + to the Uri SMIv2 textual convention defined in RFC 5017."; + reference + "RFC 3986: Uniform Resource Identifier (URI): Generic Syntax + RFC 3305: Report from the Joint W3C/IETF URI Planning Interest + Group: Uniform Resource Identifiers (URIs), URLs, + and Uniform Resource Names (URNs): Clarifications + and Recommendations + RFC 5017: MIB Textual Conventions for Uniform Resource + Identifiers (URIs)"; + } + +} diff --git a/models/yang/common/ietf-interfaces.yang b/models/yang/common/ietf-interfaces.yang new file mode 100644 index 000000000..ff2586abe --- /dev/null +++ b/models/yang/common/ietf-interfaces.yang @@ -0,0 +1,1121 @@ +module ietf-interfaces { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces"; + prefix if; + + import ietf-yang-types { + prefix yang; + } + + organization + "IETF NETMOD (Network Modeling) Working Group"; + + contact + "WG Web: + WG List: + + Editor: Martin Bjorklund + "; + + description + "This module contains a collection of YANG definitions for + managing network interfaces. + + Copyright (c) 2018 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8343; see + the RFC itself for full legal notices."; + + revision 2018-02-20 { + description + "Updated to support NMDA."; + reference + "RFC 8343: A YANG Data Model for Interface Management"; + } + + revision 2014-05-08 { + description + "Initial revision."; + reference + "RFC 7223: A YANG Data Model for Interface Management"; + } + + /* + * Typedefs + */ + + typedef interface-ref { + type leafref { + path "/if:interfaces/if:interface/if:name"; + } + description + "This type is used by data models that need to reference + interfaces."; + } + + /* + * Identities + */ + + identity interface-type { + description + "Base identity from which specific interface types are + derived."; + } + + /* + * Features + */ + + feature arbitrary-names { + description + "This feature indicates that the device allows user-controlled + interfaces to be named arbitrarily."; + } + feature pre-provisioning { + description + "This feature indicates that the device supports + pre-provisioning of interface configuration, i.e., it is + possible to configure an interface whose physical interface + hardware is not present on the device."; + } + feature if-mib { + description + "This feature indicates that the device implements + the IF-MIB."; + reference + "RFC 2863: The Interfaces Group MIB"; + } + + /* + * Data nodes + */ + + container interfaces { + description + "Interface parameters."; + + list interface { + key "name"; + + description + "The list of interfaces on the device. + + The status of an interface is available in this list in the + operational state. If the configuration of a + system-controlled interface cannot be used by the system + (e.g., the interface hardware present does not match the + interface type), then the configuration is not applied to + the system-controlled interface shown in the operational + state. If the configuration of a user-controlled interface + cannot be used by the system, the configured interface is + not instantiated in the operational state. + + System-controlled interfaces created by the system are + always present in this list in the operational state, + whether or not they are configured."; + + leaf name { + type string; + description + "The name of the interface. + + A device MAY restrict the allowed values for this leaf, + possibly depending on the type of the interface. + For system-controlled interfaces, this leaf is the + device-specific name of the interface. + + If a client tries to create configuration for a + system-controlled interface that is not present in the + operational state, the server MAY reject the request if + the implementation does not support pre-provisioning of + interfaces or if the name refers to an interface that can + never exist in the system. A Network Configuration + Protocol (NETCONF) server MUST reply with an rpc-error + with the error-tag 'invalid-value' in this case. + + If the device supports pre-provisioning of interface + configuration, the 'pre-provisioning' feature is + advertised. + + If the device allows arbitrarily named user-controlled + interfaces, the 'arbitrary-names' feature is advertised. + When a configured user-controlled interface is created by + the system, it is instantiated with the same name in the + operational state. + + A server implementation MAY map this leaf to the ifName + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifName. The definition of + such a mechanism is outside the scope of this document."; + reference + "RFC 2863: The Interfaces Group MIB - ifName"; + } + + leaf description { + type string; + description + "A textual description of the interface. + + A server implementation MAY map this leaf to the ifAlias + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifAlias. The definition of + such a mechanism is outside the scope of this document. + + Since ifAlias is defined to be stored in non-volatile + storage, the MIB implementation MUST map ifAlias to the + value of 'description' in the persistently stored + configuration."; + reference + "RFC 2863: The Interfaces Group MIB - ifAlias"; + } + + leaf type { + type identityref { + base interface-type; + } + mandatory true; + description + "The type of the interface. + + When an interface entry is created, a server MAY + initialize the type leaf with a valid value, e.g., if it + is possible to derive the type from the name of the + interface. + + If a client tries to set the type of an interface to a + value that can never be used by the system, e.g., if the + type is not supported or if the type does not match the + name of the interface, the server MUST reject the request. + A NETCONF server MUST reply with an rpc-error with the + error-tag 'invalid-value' in this case."; + reference + "RFC 2863: The Interfaces Group MIB - ifType"; + } + + leaf enabled { + type boolean; + default "true"; + description + "This leaf contains the configured, desired state of the + interface. + + Systems that implement the IF-MIB use the value of this + leaf in the intended configuration to set + IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry + has been initialized, as described in RFC 2863. + + Changes in this leaf in the intended configuration are + reflected in ifAdminStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + } + + leaf link-up-down-trap-enable { + if-feature if-mib; + type enumeration { + enum enabled { + value 1; + description + "The device will generate linkUp/linkDown SNMP + notifications for this interface."; + } + enum disabled { + value 2; + description + "The device will not generate linkUp/linkDown SNMP + notifications for this interface."; + } + } + description + "Controls whether linkUp/linkDown SNMP notifications + should be generated for this interface. + + If this node is not configured, the value 'enabled' is + operationally used by the server for interfaces that do + not operate on top of any other interface (i.e., there are + no 'lower-layer-if' entries), and 'disabled' otherwise."; + reference + "RFC 2863: The Interfaces Group MIB - + ifLinkUpDownTrapEnable"; + } + + leaf admin-status { + if-feature if-mib; + type enumeration { + enum up { + value 1; + description + "Ready to pass packets."; + } + enum down { + value 2; + description + "Not ready to pass packets and not in some test mode."; + } + enum testing { + value 3; + description + "In some test mode."; + } + } + config false; + mandatory true; + description + "The desired state of the interface. + + This leaf has the same read semantics as ifAdminStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + } + + leaf oper-status { + type enumeration { + enum up { + value 1; + description + "Ready to pass packets."; + } + enum down { + value 2; + + description + "The interface does not pass any packets."; + } + enum testing { + value 3; + description + "In some test mode. No operational packets can + be passed."; + } + enum unknown { + value 4; + description + "Status cannot be determined for some reason."; + } + enum dormant { + value 5; + description + "Waiting for some external event."; + } + enum not-present { + value 6; + description + "Some component (typically hardware) is missing."; + } + enum lower-layer-down { + value 7; + description + "Down due to state of lower-layer interface(s)."; + } + } + config false; + mandatory true; + description + "The current operational state of the interface. + + This leaf has the same semantics as ifOperStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifOperStatus"; + } + + leaf last-change { + type yang:date-and-time; + config false; + description + "The time the interface entered its current operational + state. If the current state was entered prior to the + last re-initialization of the local network management + subsystem, then this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifLastChange"; + } + + leaf if-index { + if-feature if-mib; + type int32 { + range "1..2147483647"; + } + config false; + mandatory true; + description + "The ifIndex value for the ifEntry represented by this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifIndex"; + } + + leaf phys-address { + type yang:phys-address; + config false; + description + "The interface's address at its protocol sub-layer. For + example, for an 802.x interface, this object normally + contains a Media Access Control (MAC) address. The + interface's media-specific modules must define the bit + and byte ordering and the format of the value of this + object. For interfaces that do not have such an address + (e.g., a serial line), this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifPhysAddress"; + } + + leaf-list higher-layer-if { + type interface-ref; + config false; + description + "A list of references to interfaces layered on top of this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + } + + leaf-list lower-layer-if { + type interface-ref; + config false; + description + "A list of references to interfaces layered underneath this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + } + + leaf speed { + type yang:gauge64; + units "bits/second"; + config false; + description + "An estimate of the interface's current bandwidth in bits + per second. For interfaces that do not vary in + bandwidth or for those where no accurate estimation can + be made, this node should contain the nominal bandwidth. + For interfaces that have no concept of bandwidth, this + node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - + ifSpeed, ifHighSpeed"; + } + + container statistics { + config false; + description + "A collection of interface-related statistics objects."; + + leaf discontinuity-time { + type yang:date-and-time; + mandatory true; + description + "The time on the most recent occasion at which any one or + more of this interface's counters suffered a + discontinuity. If no such discontinuities have occurred + since the last re-initialization of the local management + subsystem, then this node contains the time the local + management subsystem re-initialized itself."; + } + + leaf in-octets { + type yang:counter64; + description + "The total number of octets received on the interface, + including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; + } + + leaf in-unicast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were not addressed to a + multicast or broadcast address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; + } + + leaf in-broadcast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a broadcast + address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInBroadcastPkts"; + } + + leaf in-multicast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a multicast + address at this sub-layer. For a MAC-layer protocol, + this includes both Group and Functional addresses. + + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInMulticastPkts"; + } + + leaf in-discards { + type yang:counter32; + description + "The number of inbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being deliverable to a higher-layer + protocol. One possible reason for discarding such a + packet could be to free up buffer space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInDiscards"; + } + + leaf in-errors { + type yang:counter32; + description + "For packet-oriented interfaces, the number of inbound + packets that contained errors preventing them from being + deliverable to a higher-layer protocol. For character- + oriented or fixed-length interfaces, the number of + inbound transmission units that contained errors + preventing them from being deliverable to a higher-layer + protocol. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInErrors"; + } + + leaf in-unknown-protos { + type yang:counter32; + + description + "For packet-oriented interfaces, the number of packets + received via the interface that were discarded because + of an unknown or unsupported protocol. For + character-oriented or fixed-length interfaces that + support protocol multiplexing, the number of + transmission units received via the interface that were + discarded because of an unknown or unsupported protocol. + For any interface that does not support protocol + multiplexing, this counter is not present. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; + } + + leaf out-octets { + type yang:counter64; + description + "The total number of octets transmitted out of the + interface, including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; + } + + leaf out-unicast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted and that were not addressed + to a multicast or broadcast address at this sub-layer, + including those that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; + } + + leaf out-broadcast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted and that were addressed to a + broadcast address at this sub-layer, including those + that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutBroadcastPkts"; + } + + leaf out-multicast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted and that were addressed to a + multicast address at this sub-layer, including those + that were discarded or not sent. For a MAC-layer + protocol, this includes both Group and Functional + addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutMulticastPkts"; + } + + leaf out-discards { + type yang:counter32; + description + "The number of outbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being transmitted. One possible reason + for discarding such a packet could be to free up buffer + space. + + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; + } + + leaf out-errors { + type yang:counter32; + description + "For packet-oriented interfaces, the number of outbound + packets that could not be transmitted because of errors. + For character-oriented or fixed-length interfaces, the + number of outbound transmission units that could not be + transmitted because of errors. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutErrors"; + } + } + + } + } + + /* + * Legacy typedefs + */ + + typedef interface-state-ref { + type leafref { + path "/if:interfaces-state/if:interface/if:name"; + } + status deprecated; + description + "This type is used by data models that need to reference + the operationally present interfaces."; + } + + /* + * Legacy operational state data nodes + */ + + container interfaces-state { + config false; + status deprecated; + description + "Data nodes for the operational state of interfaces."; + + list interface { + key "name"; + status deprecated; + + description + "The list of interfaces on the device. + + System-controlled interfaces created by the system are + always present in this list, whether or not they are + configured."; + + leaf name { + type string; + status deprecated; + description + "The name of the interface. + + A server implementation MAY map this leaf to the ifName + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifName. The definition of + such a mechanism is outside the scope of this document."; + reference + "RFC 2863: The Interfaces Group MIB - ifName"; + } + + leaf type { + type identityref { + base interface-type; + } + mandatory true; + status deprecated; + description + "The type of the interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifType"; + } + + leaf admin-status { + if-feature if-mib; + type enumeration { + enum up { + value 1; + description + "Ready to pass packets."; + } + enum down { + value 2; + description + "Not ready to pass packets and not in some test mode."; + } + enum testing { + value 3; + description + "In some test mode."; + } + } + mandatory true; + status deprecated; + description + "The desired state of the interface. + + This leaf has the same read semantics as ifAdminStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + } + + leaf oper-status { + type enumeration { + enum up { + value 1; + description + "Ready to pass packets."; + } + enum down { + value 2; + description + "The interface does not pass any packets."; + } + enum testing { + value 3; + description + "In some test mode. No operational packets can + be passed."; + } + enum unknown { + value 4; + description + "Status cannot be determined for some reason."; + } + enum dormant { + value 5; + description + "Waiting for some external event."; + } + enum not-present { + value 6; + description + "Some component (typically hardware) is missing."; + } + enum lower-layer-down { + value 7; + description + "Down due to state of lower-layer interface(s)."; + } + } + mandatory true; + status deprecated; + description + "The current operational state of the interface. + + This leaf has the same semantics as ifOperStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifOperStatus"; + } + + leaf last-change { + type yang:date-and-time; + status deprecated; + description + "The time the interface entered its current operational + state. If the current state was entered prior to the + last re-initialization of the local network management + subsystem, then this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifLastChange"; + } + + leaf if-index { + if-feature if-mib; + type int32 { + range "1..2147483647"; + } + mandatory true; + status deprecated; + description + "The ifIndex value for the ifEntry represented by this + interface."; + + reference + "RFC 2863: The Interfaces Group MIB - ifIndex"; + } + + leaf phys-address { + type yang:phys-address; + status deprecated; + description + "The interface's address at its protocol sub-layer. For + example, for an 802.x interface, this object normally + contains a Media Access Control (MAC) address. The + interface's media-specific modules must define the bit + and byte ordering and the format of the value of this + object. For interfaces that do not have such an address + (e.g., a serial line), this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifPhysAddress"; + } + + leaf-list higher-layer-if { + type interface-state-ref; + status deprecated; + description + "A list of references to interfaces layered on top of this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + } + + leaf-list lower-layer-if { + type interface-state-ref; + status deprecated; + description + "A list of references to interfaces layered underneath this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + } + + leaf speed { + type yang:gauge64; + units "bits/second"; + status deprecated; + description + "An estimate of the interface's current bandwidth in bits + per second. For interfaces that do not vary in + bandwidth or for those where no accurate estimation can + + be made, this node should contain the nominal bandwidth. + For interfaces that have no concept of bandwidth, this + node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - + ifSpeed, ifHighSpeed"; + } + + container statistics { + status deprecated; + description + "A collection of interface-related statistics objects."; + + leaf discontinuity-time { + type yang:date-and-time; + mandatory true; + status deprecated; + description + "The time on the most recent occasion at which any one or + more of this interface's counters suffered a + discontinuity. If no such discontinuities have occurred + since the last re-initialization of the local management + subsystem, then this node contains the time the local + management subsystem re-initialized itself."; + } + + leaf in-octets { + type yang:counter64; + status deprecated; + description + "The total number of octets received on the interface, + including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; + } + + leaf in-unicast-pkts { + type yang:counter64; + status deprecated; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were not addressed to a + multicast or broadcast address at this sub-layer. + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; + } + + leaf in-broadcast-pkts { + type yang:counter64; + status deprecated; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a broadcast + address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInBroadcastPkts"; + } + + leaf in-multicast-pkts { + type yang:counter64; + status deprecated; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a multicast + address at this sub-layer. For a MAC-layer protocol, + this includes both Group and Functional addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInMulticastPkts"; + } + + leaf in-discards { + type yang:counter32; + status deprecated; + description + "The number of inbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being deliverable to a higher-layer + protocol. One possible reason for discarding such a + packet could be to free up buffer space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInDiscards"; + } + + leaf in-errors { + type yang:counter32; + status deprecated; + description + "For packet-oriented interfaces, the number of inbound + packets that contained errors preventing them from being + deliverable to a higher-layer protocol. For character- + oriented or fixed-length interfaces, the number of + inbound transmission units that contained errors + preventing them from being deliverable to a higher-layer + protocol. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInErrors"; + } + + leaf in-unknown-protos { + type yang:counter32; + status deprecated; + description + "For packet-oriented interfaces, the number of packets + received via the interface that were discarded because + of an unknown or unsupported protocol. For + character-oriented or fixed-length interfaces that + support protocol multiplexing, the number of + transmission units received via the interface that were + discarded because of an unknown or unsupported protocol. + For any interface that does not support protocol + multiplexing, this counter is not present. + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; + } + + leaf out-octets { + type yang:counter64; + status deprecated; + description + "The total number of octets transmitted out of the + interface, including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; + } + + leaf out-unicast-pkts { + type yang:counter64; + status deprecated; + description + "The total number of packets that higher-level protocols + requested be transmitted and that were not addressed + to a multicast or broadcast address at this sub-layer, + including those that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; + } + + leaf out-broadcast-pkts { + type yang:counter64; + status deprecated; + description + "The total number of packets that higher-level protocols + requested be transmitted and that were addressed to a + broadcast address at this sub-layer, including those + that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutBroadcastPkts"; + } + + leaf out-multicast-pkts { + type yang:counter64; + status deprecated; + description + "The total number of packets that higher-level protocols + requested be transmitted and that were addressed to a + multicast address at this sub-layer, including those + that were discarded or not sent. For a MAC-layer + protocol, this includes both Group and Functional + addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutMulticastPkts"; + } + + leaf out-discards { + type yang:counter32; + status deprecated; + description + "The number of outbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being transmitted. One possible reason + for discarding such a packet could be to free up buffer + space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; + } + + leaf out-errors { + type yang:counter32; + status deprecated; + description + "For packet-oriented interfaces, the number of outbound + packets that could not be transmitted because of errors. + For character-oriented or fixed-length interfaces, the + number of outbound transmission units that could not be + transmitted because of errors. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutErrors"; + } + } + } + } +} diff --git a/models/yang/common/ietf-yang-types.yang b/models/yang/common/ietf-yang-types.yang new file mode 100644 index 000000000..ee58fa3ab --- /dev/null +++ b/models/yang/common/ietf-yang-types.yang @@ -0,0 +1,474 @@ +module ietf-yang-types { + + namespace "urn:ietf:params:xml:ns:yang:ietf-yang-types"; + prefix "yang"; + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: David Kessens + + + WG Chair: Juergen Schoenwaelder + + + Editor: Juergen Schoenwaelder + "; + + description + "This module contains a collection of generally useful derived + YANG data types. + + Copyright (c) 2013 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6991; see + the RFC itself for full legal notices."; + + revision 2013-07-15 { + description + "This revision adds the following new data types: + - yang-identifier + - hex-string + - uuid + - dotted-quad"; + reference + "RFC 6991: Common YANG Data Types"; + } + + revision 2010-09-24 { + description + "Initial revision."; + reference + "RFC 6021: Common YANG Data Types"; + } + + /*** collection of counter and gauge types ***/ + + typedef counter32 { + type uint32; + description + "The counter32 type represents a non-negative integer + that monotonically increases until it reaches a + maximum value of 2^32-1 (4294967295 decimal), when it + wraps around and starts increasing again from zero. + + Counters have no defined 'initial' value, and thus, a + single value of a counter has (in general) no information + content. Discontinuities in the monotonically increasing + value normally occur at re-initialization of the + management system, and at other times as specified in the + description of a schema node using this type. If such + other times can occur, for example, the creation of + a schema node of type counter32 at times other than + re-initialization, then a corresponding schema node + should be defined, with an appropriate type, to indicate + the last discontinuity. + + The counter32 type should not be used for configuration + schema nodes. A default statement SHOULD NOT be used in + combination with the type counter32. + + In the value set and its semantics, this type is equivalent + to the Counter32 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef zero-based-counter32 { + type yang:counter32; + default "0"; + description + "The zero-based-counter32 type represents a counter32 + that has the defined 'initial' value zero. + + A schema node of this type will be set to zero (0) on creation + and will thereafter increase monotonically until it reaches + a maximum value of 2^32-1 (4294967295 decimal), when it + wraps around and starts increasing again from zero. + + Provided that an application discovers a new schema node + of this type within the minimum time to wrap, it can use the + 'initial' value as a delta. It is important for a management + station to be aware of this minimum time and the actual time + between polls, and to discard data if the actual time is too + long or there is no defined minimum time. + + In the value set and its semantics, this type is equivalent + to the ZeroBasedCounter32 textual convention of the SMIv2."; + reference + "RFC 4502: Remote Network Monitoring Management Information + Base Version 2"; + } + + typedef counter64 { + type uint64; + description + "The counter64 type represents a non-negative integer + that monotonically increases until it reaches a + maximum value of 2^64-1 (18446744073709551615 decimal), + when it wraps around and starts increasing again from zero. + + Counters have no defined 'initial' value, and thus, a + single value of a counter has (in general) no information + content. Discontinuities in the monotonically increasing + value normally occur at re-initialization of the + management system, and at other times as specified in the + description of a schema node using this type. If such + other times can occur, for example, the creation of + a schema node of type counter64 at times other than + re-initialization, then a corresponding schema node + should be defined, with an appropriate type, to indicate + the last discontinuity. + + The counter64 type should not be used for configuration + schema nodes. A default statement SHOULD NOT be used in + combination with the type counter64. + + In the value set and its semantics, this type is equivalent + to the Counter64 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef zero-based-counter64 { + type yang:counter64; + default "0"; + description + "The zero-based-counter64 type represents a counter64 that + has the defined 'initial' value zero. + + A schema node of this type will be set to zero (0) on creation + and will thereafter increase monotonically until it reaches + a maximum value of 2^64-1 (18446744073709551615 decimal), + when it wraps around and starts increasing again from zero. + + Provided that an application discovers a new schema node + of this type within the minimum time to wrap, it can use the + 'initial' value as a delta. It is important for a management + station to be aware of this minimum time and the actual time + between polls, and to discard data if the actual time is too + long or there is no defined minimum time. + + In the value set and its semantics, this type is equivalent + to the ZeroBasedCounter64 textual convention of the SMIv2."; + reference + "RFC 2856: Textual Conventions for Additional High Capacity + Data Types"; + } + + typedef gauge32 { + type uint32; + description + "The gauge32 type represents a non-negative integer, which + may increase or decrease, but shall never exceed a maximum + value, nor fall below a minimum value. The maximum value + cannot be greater than 2^32-1 (4294967295 decimal), and + the minimum value cannot be smaller than 0. The value of + a gauge32 has its maximum value whenever the information + being modeled is greater than or equal to its maximum + value, and has its minimum value whenever the information + being modeled is smaller than or equal to its minimum value. + If the information being modeled subsequently decreases + below (increases above) the maximum (minimum) value, the + gauge32 also decreases (increases). + + In the value set and its semantics, this type is equivalent + to the Gauge32 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef gauge64 { + type uint64; + description + "The gauge64 type represents a non-negative integer, which + may increase or decrease, but shall never exceed a maximum + value, nor fall below a minimum value. The maximum value + cannot be greater than 2^64-1 (18446744073709551615), and + the minimum value cannot be smaller than 0. The value of + a gauge64 has its maximum value whenever the information + being modeled is greater than or equal to its maximum + value, and has its minimum value whenever the information + being modeled is smaller than or equal to its minimum value. + If the information being modeled subsequently decreases + below (increases above) the maximum (minimum) value, the + gauge64 also decreases (increases). + + In the value set and its semantics, this type is equivalent + to the CounterBasedGauge64 SMIv2 textual convention defined + in RFC 2856"; + reference + "RFC 2856: Textual Conventions for Additional High Capacity + Data Types"; + } + + /*** collection of identifier-related types ***/ + + typedef object-identifier { + type string { + pattern '(([0-1](\.[1-3]?[0-9]))|(2\.(0|([1-9]\d*))))' + + '(\.(0|([1-9]\d*)))*'; + } + description + "The object-identifier type represents administratively + assigned names in a registration-hierarchical-name tree. + + Values of this type are denoted as a sequence of numerical + non-negative sub-identifier values. Each sub-identifier + value MUST NOT exceed 2^32-1 (4294967295). Sub-identifiers + are separated by single dots and without any intermediate + whitespace. + + The ASN.1 standard restricts the value space of the first + sub-identifier to 0, 1, or 2. Furthermore, the value space + of the second sub-identifier is restricted to the range + 0 to 39 if the first sub-identifier is 0 or 1. Finally, + the ASN.1 standard requires that an object identifier + has always at least two sub-identifiers. The pattern + captures these restrictions. + + Although the number of sub-identifiers is not limited, + module designers should realize that there may be + implementations that stick with the SMIv2 limit of 128 + sub-identifiers. + + This type is a superset of the SMIv2 OBJECT IDENTIFIER type + since it is not restricted to 128 sub-identifiers. Hence, + this type SHOULD NOT be used to represent the SMIv2 OBJECT + IDENTIFIER type; the object-identifier-128 type SHOULD be + used instead."; + reference + "ISO9834-1: Information technology -- Open Systems + Interconnection -- Procedures for the operation of OSI + Registration Authorities: General procedures and top + arcs of the ASN.1 Object Identifier tree"; + } + + typedef object-identifier-128 { + type object-identifier { + pattern '\d*(\.\d*){1,127}'; + } + description + "This type represents object-identifiers restricted to 128 + sub-identifiers. + + In the value set and its semantics, this type is equivalent + to the OBJECT IDENTIFIER type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef yang-identifier { + type string { + length "1..max"; + pattern '[a-zA-Z_][a-zA-Z0-9\-_.]*'; + pattern '.|..|[^xX].*|.[^mM].*|..[^lL].*'; + } + description + "A YANG identifier string as defined by the 'identifier' + rule in Section 12 of RFC 6020. An identifier must + start with an alphabetic character or an underscore + followed by an arbitrary sequence of alphabetic or + numeric characters, underscores, hyphens, or dots. + + A YANG identifier MUST NOT start with any possible + combination of the lowercase or uppercase character + sequence 'xml'."; + reference + "RFC 6020: YANG - A Data Modeling Language for the Network + Configuration Protocol (NETCONF)"; + } + + /*** collection of types related to date and time***/ + + typedef date-and-time { + type string { + pattern '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?' + + '(Z|[\+\-]\d{2}:\d{2})'; + } + description + "The date-and-time type is a profile of the ISO 8601 + standard for representation of dates and times using the + Gregorian calendar. The profile is defined by the + date-time production in Section 5.6 of RFC 3339. + + The date-and-time type is compatible with the dateTime XML + schema type with the following notable exceptions: + + (a) The date-and-time type does not allow negative years. + + (b) The date-and-time time-offset -00:00 indicates an unknown + time zone (see RFC 3339) while -00:00 and +00:00 and Z + all represent the same time zone in dateTime. + + (c) The canonical format (see below) of data-and-time values + differs from the canonical format used by the dateTime XML + schema type, which requires all times to be in UTC using + the time-offset 'Z'. + + This type is not equivalent to the DateAndTime textual + convention of the SMIv2 since RFC 3339 uses a different + separator between full-date and full-time and provides + higher resolution of time-secfrac. + + The canonical format for date-and-time values with a known time + zone uses a numeric time zone offset that is calculated using + the device's configured known offset to UTC time. A change of + the device's offset to UTC time will cause date-and-time values + to change accordingly. Such changes might happen periodically + in case a server follows automatically daylight saving time + (DST) time zone offset changes. The canonical format for + date-and-time values with an unknown time zone (usually + referring to the notion of local time) uses the time-offset + -00:00."; + reference + "RFC 3339: Date and Time on the Internet: Timestamps + RFC 2579: Textual Conventions for SMIv2 + XSD-TYPES: XML Schema Part 2: Datatypes Second Edition"; + } + + typedef timeticks { + type uint32; + description + "The timeticks type represents a non-negative integer that + represents the time, modulo 2^32 (4294967296 decimal), in + hundredths of a second between two epochs. When a schema + node is defined that uses this type, the description of + the schema node identifies both of the reference epochs. + + In the value set and its semantics, this type is equivalent + to the TimeTicks type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef timestamp { + type yang:timeticks; + description + "The timestamp type represents the value of an associated + timeticks schema node at which a specific occurrence + happened. The specific occurrence must be defined in the + description of any schema node defined using this type. When + the specific occurrence occurred prior to the last time the + associated timeticks attribute was zero, then the timestamp + value is zero. Note that this requires all timestamp values + to be reset to zero when the value of the associated timeticks + attribute reaches 497+ days and wraps around to zero. + + The associated timeticks schema node must be specified + in the description of any schema node using this type. + + In the value set and its semantics, this type is equivalent + to the TimeStamp textual convention of the SMIv2."; + reference + "RFC 2579: Textual Conventions for SMIv2"; + } + + /*** collection of generic address types ***/ + + typedef phys-address { + type string { + pattern '([0-9a-fA-F]{2}(:[0-9a-fA-F]{2})*)?'; + } + + description + "Represents media- or physical-level addresses represented + as a sequence octets, each octet represented by two hexadecimal + numbers. Octets are separated by colons. The canonical + representation uses lowercase characters. + + In the value set and its semantics, this type is equivalent + to the PhysAddress textual convention of the SMIv2."; + reference + "RFC 2579: Textual Conventions for SMIv2"; + } + + typedef mac-address { + type string { + pattern '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'; + } + description + "The mac-address type represents an IEEE 802 MAC address. + The canonical representation uses lowercase characters. + + In the value set and its semantics, this type is equivalent + to the MacAddress textual convention of the SMIv2."; + reference + "IEEE 802: IEEE Standard for Local and Metropolitan Area + Networks: Overview and Architecture + RFC 2579: Textual Conventions for SMIv2"; + } + + /*** collection of XML-specific types ***/ + + typedef xpath1.0 { + type string; + description + "This type represents an XPATH 1.0 expression. + + When a schema node is defined that uses this type, the + description of the schema node MUST specify the XPath + context in which the XPath expression is evaluated."; + reference + "XPATH: XML Path Language (XPath) Version 1.0"; + } + + /*** collection of string types ***/ + + typedef hex-string { + type string { + pattern '([0-9a-fA-F]{2}(:[0-9a-fA-F]{2})*)?'; + } + description + "A hexadecimal string with octets represented as hex digits + separated by colons. The canonical representation uses + lowercase characters."; + } + + typedef uuid { + type string { + pattern '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-' + + '[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'; + } + description + "A Universally Unique IDentifier in the string representation + defined in RFC 4122. The canonical representation uses + lowercase characters. + + The following is an example of a UUID in string representation: + f81d4fae-7dec-11d0-a765-00a0c91e6bf6 + "; + reference + "RFC 4122: A Universally Unique IDentifier (UUID) URN + Namespace"; + } + + typedef dotted-quad { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'; + } + description + "An unsigned 32-bit number expressed in the dotted-quad + notation, i.e., four octets written as decimal numbers + and separated with the '.' (full stop) character."; + } +} diff --git a/models/yang/common/openconfig-aaa-radius.yang b/models/yang/common/openconfig-aaa-radius.yang new file mode 100644 index 000000000..a18b9d68d --- /dev/null +++ b/models/yang/common/openconfig-aaa-radius.yang @@ -0,0 +1,186 @@ +submodule openconfig-aaa-radius { + + yang-version "1"; + + belongs-to "openconfig-aaa" { + prefix "oc-aaa"; + } + + // import some basic types + import openconfig-inet-types { prefix oc-inet; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-aaa-types { prefix oc-aaa-types; } + import openconfig-types { prefix oc-types; } + import openconfig-yang-types { prefix oc-yang; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + related to the RADIUS protocol for authentication, + authorization, and accounting."; + + oc-ext:openconfig-version "0.4.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.4.1"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // extension statements + + // feature statements + + // identity statements + + identity RADIUS { + base oc-aaa-types:AAA_SERVER_TYPE; + description + "Remote Authentication Dial In User Service (RADIUS) AAA + server"; + reference + "RFC 2865 - Remote Authentication Dial In User Service + (RADIUS)"; + } + + // typedef statements + + // grouping statements + + grouping aaa-radius-server-config { + description + "Configuration data for a RADIUS server"; + + leaf auth-port { + type oc-inet:port-number; + default 1812; + description + "Port number for authentication requests"; + } + + leaf acct-port { + type oc-inet:port-number; + default 1813; + description + "Port number for accounting requests"; + } + + leaf secret-key { + type oc-types:routing-password; + description + "The unencrypted shared key used between the authentication + server and the device."; + } + + leaf source-address { + type oc-inet:ip-address; + description + "Source IP address to use in messages to the RADIUS server"; + } + + leaf retransmit-attempts { + type uint8; + description + "Number of times the system may resend a request to the + RADIUS server when it is unresponsive"; + } + } + + grouping aaa-radius-server-state { + description + "Operational state data for a RADIUS server"; + + container counters { + description + "A collection of RADIUS related state objects."; + + leaf retried-access-requests { + type oc-yang:counter64; + description + "Retransmitted Access-Request messages."; + } + + leaf access-accepts { + type oc-yang:counter64; + description + "Received Access-Accept messages."; + } + + leaf access-rejects { + type oc-yang:counter64; + description + "Received Access-Reject messages."; + } + + leaf timeout-access-requests { + type oc-yang:counter64; + description + "Access-Request messages that have timed-out, + requiring retransmission."; + } + } + } + + grouping aaa-radius-server-top { + description + "Top-level grouping for RADIUS server data"; + + container radius { + description + "Top-level container for RADIUS server data"; + + container config { + description + "Configuration data for RADIUS servers"; + + uses aaa-radius-server-config; + } + + container state { + + config false; + + description + "Operational state data for RADIUS servers"; + + uses aaa-radius-server-config; + uses aaa-radius-server-state; + } + } + } + + // data definition statements + + // augment statements + + // rpc statements + + // notification statements + +} diff --git a/models/yang/common/openconfig-aaa-tacacs.yang b/models/yang/common/openconfig-aaa-tacacs.yang new file mode 100644 index 000000000..1320bd0cf --- /dev/null +++ b/models/yang/common/openconfig-aaa-tacacs.yang @@ -0,0 +1,142 @@ +submodule openconfig-aaa-tacacs { + + yang-version "1"; + + belongs-to "openconfig-aaa" { + prefix "oc-aaa"; + } + + // import some basic types + import openconfig-inet-types { prefix oc-inet; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-aaa-types { prefix oc-aaa-types; } + import openconfig-types { prefix oc-types; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + related to the TACACS+ protocol for authentication, + authorization, and accounting."; + + oc-ext:openconfig-version "0.4.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.4.1"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // extension statements + + // feature statements + + // identity statements + + identity TACACS { + base oc-aaa-types:AAA_SERVER_TYPE; + description + "Terminal Access Controller Access Control System (TACACS+) + AAA server"; + reference + "The TACACS+ Protocol (draft-ietf-opsawg-tacacs-05) + RFC 1492 - An Access Control Protocol, Sometimes Called + TACACS"; + } + + // typedef statements + + // grouping statements + + grouping aaa-tacacs-server-config { + description + "Configuration data for a TACACS+ server"; + + leaf port { + type oc-inet:port-number; + default 49; + description + "The port number on which to contact the TACACS server"; + } + + leaf secret-key { + type oc-types:routing-password; + description + "The unencrypted shared key used between the authentication + server and the device."; + } + + leaf source-address { + type oc-inet:ip-address; + description + "Source IP address to use in messages to the TACACS server"; + } + } + + grouping aaa-tacacs-server-state { + description + "Operational state data for a TACACS+ server"; + } + + grouping aaa-tacacs-server-top { + description + "Top-level grouping for TACACS+ sever data"; + + container tacacs { + description + "Top-level container for TACACS+ server data"; + + container config { + description + "Configuration data for TACACS+ server"; + + uses aaa-tacacs-server-config; + } + + container state { + + config false; + + description + "Operational state data for TACACS+ server"; + + uses aaa-tacacs-server-config; + uses aaa-tacacs-server-state; + } + } + } + + // data definition statements + + // augment statements + + // rpc statements + + // notification statements + +} diff --git a/models/yang/common/openconfig-aaa-types.yang b/models/yang/common/openconfig-aaa-types.yang new file mode 100644 index 000000000..8385eca79 --- /dev/null +++ b/models/yang/common/openconfig-aaa-types.yang @@ -0,0 +1,172 @@ +module openconfig-aaa-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/aaa/types"; + + prefix "oc-aaa-types"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines shared types for data related to AAA + (authentication, authorization, accounting)."; + + oc-ext:openconfig-version "0.4.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.4.1"; + } + + revision "2018-04-12" { + description + "Add when conditions, correct identities"; + reference "0.4.0"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + + identity AAA_SERVER_TYPE { + description + "Base identity for types of AAA servers"; + } + + + identity SYSTEM_DEFINED_ROLES { + description + "Base identity for system_defined roles that can be assigned + to users."; + } + + identity SYSTEM_ROLE_ADMIN { + base SYSTEM_DEFINED_ROLES; + description + "Built-in role that allows the equivalent of superuser + permission for all configuration and operational commands + on the device."; + } + + identity AAA_ACCOUNTING_EVENT_TYPE { + description + "Base identity for specifying events types that should be + sent to AAA server for accounting"; + } + + identity AAA_ACCOUNTING_EVENT_COMMAND { + base AAA_ACCOUNTING_EVENT_TYPE; + description + "Specifies interactive command events for AAA accounting"; + } + + identity AAA_ACCOUNTING_EVENT_LOGIN { + base AAA_ACCOUNTING_EVENT_TYPE; + description + "Specifies login events for AAA accounting"; + } + + identity AAA_AUTHORIZATION_EVENT_TYPE { + description + "Base identity for specifying activities that should be + sent to AAA server for authorization"; + } + + identity AAA_AUTHORIZATION_EVENT_COMMAND { + base AAA_AUTHORIZATION_EVENT_TYPE; + description + "Specifies interactive command events for AAA authorization"; + } + + identity AAA_AUTHORIZATION_EVENT_CONFIG { + base AAA_AUTHORIZATION_EVENT_TYPE; + description + "Specifies configuration (e.g., EXEC) events for AAA + authorization"; + } + + identity AAA_METHOD_TYPE { + description + "Base identity to define well-known methods for AAA + operations"; + } + + identity TACACS_ALL { + base AAA_METHOD_TYPE; + description + "The group of all TACACS+ servers."; + } + + identity RADIUS_ALL { + base AAA_METHOD_TYPE; + description + "The group of all RADIUS servers."; + } + + identity LOCAL { + base AAA_METHOD_TYPE; + description + "Locally configured method for AAA operations."; + } + + + // typedef statements + + typedef crypt-password-type { + type string; + description + "A password that is hashed based on the hash algorithm + indicated by the prefix in the string. The string + takes the following form, based on the Unix crypt function: + + $[$=(,=)*][$[$]] + + Common hash functions include: + + id | hash function + ---+--------------- + 1 | MD5 + 2a| Blowfish + 2y| Blowfish (correct handling of 8-bit chars) + 5 | SHA-256 + 6 | SHA-512 + + These may not all be supported by a target device."; + } + + +} diff --git a/models/yang/common/openconfig-aaa.yang b/models/yang/common/openconfig-aaa.yang new file mode 100644 index 000000000..7a2fffa00 --- /dev/null +++ b/models/yang/common/openconfig-aaa.yang @@ -0,0 +1,822 @@ +module openconfig-aaa { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/aaa"; + + prefix "oc-aaa"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + import openconfig-inet-types { prefix oc-inet; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-aaa-types { prefix oc-aaa-types; } + + include openconfig-aaa-tacacs; + include openconfig-aaa-radius; + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + related to authorization, authentication, and accounting (AAA) + management. + + Portions of this model reuse data definitions or structure from + RFC 7317 - A YANG Data Model for System Management"; + + oc-ext:openconfig-version "0.4.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.4.1"; + } + + revision "2018-04-12" { + description + "Add when conditions, correct identities"; + reference "0.4.0"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + + // grouping statements + grouping aaa-servergroup-common-config { + description + "Configuration data for AAA server groups"; + + leaf name { + type string; + description + "Name for the server group"; + } + + leaf type { + type identityref { + base oc-aaa-types:AAA_SERVER_TYPE; + } + description + "AAA server type -- all servers in the group must be of this + type"; + } + } + + grouping aaa-servergroup-common-state { + description + "Operational state data for AAA server groups"; + + //TODO: add list of group members as opstate + } + + grouping aaa-servergroup-common-top { + description + "Top-level grouping for AAA server groups"; + + container server-groups { + description + "Enclosing container for AAA server groups"; + + list server-group { + key "name"; + description + "List of AAA server groups. All servers in a group + must have the same type as indicated by the server + type."; + + leaf name { + type leafref { + path "../config/name"; + } + description + "Reference to configured name of the server group"; + } + + container config { + description + "Configuration data for each server group"; + + uses aaa-servergroup-common-config; + } + + container state { + config false; + + description + "Operational state data for each server group"; + + uses aaa-servergroup-common-config; + uses aaa-servergroup-common-state; + } + + uses aaa-server-top; + } + } + } + + grouping aaa-server-config { + description + "Common configuration data for AAA servers"; + + leaf name { + type string; + description + "Name assigned to the server"; + } + + + leaf address { + type oc-inet:ip-address; + description "Address of the authentication server"; + } + + leaf timeout { + type uint16; + units seconds; + description + "Set the timeout in seconds on responses from the AAA + server"; + } + } + + grouping aaa-server-state { + description + "Common operational state data for AAA servers"; + + leaf connection-opens { + type oc-yang:counter64; + description + "Number of new connection requests sent to the server, e.g. + socket open"; + } + + leaf connection-closes { + type oc-yang:counter64; + description + "Number of connection close requests sent to the server, e.g. + socket close"; + } + + leaf connection-aborts { + type oc-yang:counter64; + description + "Number of aborted connections to the server. These do + not include connections that are close gracefully."; + } + + leaf connection-failures { + type oc-yang:counter64; + description + "Number of connection failures to the server"; + } + + leaf connection-timeouts { + type oc-yang:counter64; + description + "Number of connection timeouts to the server"; + } + + leaf messages-sent { + type oc-yang:counter64; + description + "Number of messages sent to the server"; + } + + leaf messages-received { + type oc-yang:counter64; + description + "Number of messages received by the server"; + } + + leaf errors-received { + type oc-yang:counter64; + description + "Number of error messages received from the server"; + } + + } + + grouping aaa-server-top { + description + "Top-level grouping for list of AAA servers"; + + container servers { + description + "Enclosing container the list of servers"; + + list server { + key "address"; + description + "List of AAA servers"; + + leaf address { + type leafref { + path "../config/address"; + } + description + "Reference to the configured address of the AAA server"; + } + + container config { + description + "Configuration data "; + + uses aaa-server-config; + } + + container state { + config false; + + description + "Operational state data "; + + uses aaa-server-config; + uses aaa-server-state; + } + + uses aaa-tacacs-server-top { + when "../../config/type = 'oc-aaa-types:TACACS'"; + } + + uses aaa-radius-server-top { + when "../../config/type = 'oc-aaa-types:RADIUS'"; + } + } + } + } + + grouping aaa-admin-config { + description + "Configuration data for the system built-in + administrator / root user account"; + + leaf admin-password { + type string; + oc-ext:openconfig-hashed-value; + description + "The admin/root password, supplied as a cleartext string. + The system should hash and only store the password as a + hashed value."; + } + + leaf admin-password-hashed { + type oc-aaa-types:crypt-password-type; + description + "The admin/root password, supplied as a hashed value + using the notation described in the definition of the + crypt-password-type."; + } + } + + grouping aaa-admin-state { + description + "Operational state data for the root user"; + + leaf admin-username { + type string; + description + "Name of the administrator user account, e.g., admin, root, + etc."; + } + } + + grouping aaa-authentication-admin-top { + description + "Top-level grouping for root user configuration and state + data"; + + container admin-user { + description + "Top-level container for the system root or admin user + configuration and operational state"; + + container config { + description + "Configuration data for the root user account"; + + uses aaa-admin-config; + } + + container state { + config false; + + description + "Operational state data for the root user account"; + + uses aaa-admin-config; + uses aaa-admin-state; + } + } + } + grouping aaa-authentication-user-config { + description + "Configuration data for local users"; + + leaf username { + type string; + description + "Assigned username for this user"; + } + + leaf password { + type string; + oc-ext:openconfig-hashed-value; + description + "The user password, supplied as cleartext. The system + must hash the value and only store the hashed value."; + } + + leaf password-hashed { + type oc-aaa-types:crypt-password-type; + description + "The user password, supplied as a hashed value + using the notation described in the definition of the + crypt-password-type."; + } + + leaf ssh-key { + type string; + description + "SSH public key for the user (RSA or DSA)"; + } + + leaf role { + type union { + type string; + type identityref { + base oc-aaa-types:SYSTEM_DEFINED_ROLES; + } + } + description + "Role assigned to the user. The role may be supplied + as a string or a role defined by the SYSTEM_DEFINED_ROLES + identity."; + } + } + + grouping aaa-authentication-user-state { + description + "Operational state data for local users"; + } + + grouping aaa-authentication-user-top { + description + "Top-level grouping for local users"; + + container users { + description + "Enclosing container list of local users"; + + list user { + key "username"; + description + "List of local users on the system"; + + leaf username { + type leafref { + path "../config/username"; + } + description + "References the configured username for the user"; + } + + container config { + description + "Configuration data for local users"; + + uses aaa-authentication-user-config; + } + + container state { + config false; + + description + "Operational state data for local users"; + + uses aaa-authentication-user-config; + uses aaa-authentication-user-state; + } + } + + } + } + + grouping aaa-accounting-methods-common { + description + "Common definitions for accounting methods"; + + leaf-list accounting-method { + type union { + type identityref { + base oc-aaa-types:AAA_METHOD_TYPE; + } + type string; + //TODO: in YANG 1.1 this should be converted to a leafref to + //point to the server group name. + } + ordered-by user; + description + "An ordered list of methods used for AAA accounting for this + event type. The method is defined by the destination for + accounting data, which may be specified as the group of + all TACACS+/RADIUS servers, a defined server group, or + the local system."; + } + } + + + grouping aaa-accounting-events-config { + description + "Configuration data for AAA accounting events"; + + leaf event-type { + type identityref { + base oc-aaa-types:AAA_ACCOUNTING_EVENT_TYPE; + } + description + "The type of activity to record at the AAA accounting + server"; + } + + leaf record { + type enumeration { + enum START_STOP { + description + "Send START record to the accounting server at the + beginning of the activity, and STOP record at the + end of the activity."; + } + enum STOP { + description + "Send STOP record to the accounting server when the + user activity completes"; + } + } + description + "Type of record to send to the accounting server for this + activity type"; + } + } + + grouping aaa-accounting-events-state { + description + "Operational state data for accounting events"; + } + + grouping aaa-accounting-events-top { + description + "Top-level grouping for accounting events"; + + container events { + description + "Enclosing container for defining handling of events + for accounting"; + + list event { + key "event-type"; + description + "List of events subject to accounting"; + + leaf event-type { + type leafref { + path "../config/event-type"; + } + description + "Reference to the event-type being logged at the + accounting server"; + } + + container config { + description + "Configuration data for accounting events"; + + uses aaa-accounting-events-config; + } + + container state { + config false; + + description + "Operational state data for accounting events"; + + uses aaa-accounting-events-config; + uses aaa-accounting-events-state; + } + } + } + } + + grouping aaa-accounting-config { + description + "Configuration data for event accounting"; + + uses aaa-accounting-methods-common; + + } + + grouping aaa-accounting-state { + description + "Operational state data for event accounting services"; + } + + grouping aaa-accounting-top { + description + "Top-level grouping for user activity accounting"; + + container accounting { + description + "Top-level container for AAA accounting"; + + container config { + description + "Configuration data for user activity accounting."; + + uses aaa-accounting-config; + } + + container state { + config false; + + description + "Operational state data for user accounting."; + + uses aaa-accounting-config; + uses aaa-accounting-state; + } + + uses aaa-accounting-events-top; + + } + } + + grouping aaa-authorization-methods-config { + description + "Common definitions for authorization methods for global + and per-event type"; + + leaf-list authorization-method { + type union { + type identityref { + base oc-aaa-types:AAA_METHOD_TYPE; + } + type string; + } + ordered-by user; + description + "Ordered list of methods for authorizing commands. The first + method that provides a response (positive or negative) should + be used. The list may contain a well-defined method such + as the set of all TACACS or RADIUS servers, or the name of + a defined AAA server group. The system must validate + that the named server group exists."; + } + } + + grouping aaa-authorization-events-config { + description + "Configuration data for AAA authorization events"; + + leaf event-type { + type identityref { + base oc-aaa-types:AAA_AUTHORIZATION_EVENT_TYPE; + } + description + "The type of event to record at the AAA authorization + server"; + } + } + + grouping aaa-authorization-events-state { + description + "Operational state data for AAA authorization events"; + } + + grouping aaa-authorization-events-top { + description + "Top-level grouping for authorization events"; + + container events { + description + "Enclosing container for the set of events subject + to authorization"; + + list event { + key "event-type"; + description + "List of events subject to AAA authorization"; + + leaf event-type { + type leafref { + path "../config/event-type"; + } + description + "Reference to the event-type list key"; + } + + container config { + description + "Configuration data for each authorized event"; + + uses aaa-authorization-events-config; + } + + container state { + config false; + + description + "Operational state data for each authorized activity"; + + uses aaa-authorization-events-config; + uses aaa-authorization-events-state; + } + } + } + } + + grouping aaa-authorization-config { + description + "Configuration data for AAA authorization"; + + uses aaa-authorization-methods-config; + } + + grouping aaa-authorization-state { + description + "Operational state data for AAA authorization"; + } + + grouping aaa-authorization-top { + description + "Top-level grouping for AAA authorization"; + + container authorization { + description + "Top-level container for AAA authorization configuration + and operational state data"; + + container config { + description + "Configuration data for authorization based on AAA + methods"; + + uses aaa-authorization-config; + } + + container state { + config false; + + description + "Operational state data for authorization based on AAA"; + + uses aaa-authorization-config; + uses aaa-authorization-state; + } + + uses aaa-authorization-events-top; + + } + } + + grouping aaa-authentication-config { + description + "Configuration data for global authentication"; + + leaf-list authentication-method { + type union { + type identityref { + base oc-aaa-types:AAA_METHOD_TYPE; + } + type string; + //TODO: string should be a leafref to a defined + //server group. this will be possible in YANG 1.1 + //type leafref { + //path "/aaa/server-groups/server-group/config/name"; + //} + } + ordered-by user; + description + "Ordered list of authentication methods for users. This + can be either a reference to a server group, or a well- + defined designation in the AAA_METHOD_TYPE identity. If + authentication fails with one method, the next defined + method is tried -- failure of all methods results in the + user being denied access."; + } + } + + grouping aaa-authentication-state { + description + "Operational state data for global authentication"; + } + + grouping aaa-authentication-top { + description + "Top-level grouping for top-level authentication"; + + container authentication { + description + "Top-level container for global authentication data"; + + container config { + description + "Configuration data for global authentication services"; + + uses aaa-authentication-config; + } + + container state { + config false; + + description + "Operational state data for global authentication + services"; + + uses aaa-authentication-config; + uses aaa-authentication-state; + } + + uses aaa-authentication-admin-top; + uses aaa-authentication-user-top; + } + } + + grouping aaa-config { + description + "Configuration data for top level AAA"; + } + + grouping aaa-state { + description + "Operational state data for top level AAA"; + } + + grouping aaa-top { + description + "Top-level grouping for AAA services"; + + container aaa { + description + "Top-level container for AAA services"; + + container config { + description + "Configuration data for top level AAA services"; + + uses aaa-config; + } + + container state { + config false; + + description + "Operational state data for top level AAA services "; + + uses aaa-config; + uses aaa-state; + } + + uses aaa-authentication-top; + uses aaa-authorization-top; + uses aaa-accounting-top; + uses aaa-servergroup-common-top; + + } + } + + + + // data definition statements + + +} diff --git a/models/yang/common/openconfig-alarm-types.yang b/models/yang/common/openconfig-alarm-types.yang new file mode 100644 index 000000000..c4617b5e6 --- /dev/null +++ b/models/yang/common/openconfig-alarm-types.yang @@ -0,0 +1,150 @@ +module openconfig-alarm-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/alarms/types"; + + prefix "oc-alarm-types"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines operational state data related to alarms + that the device is reporting. + + This model reuses some data items defined in the draft IETF + YANG Alarm Module: + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02 + + Portions of this code were derived from the draft IETF YANG Alarm + Module. Please reproduce this note if possible. + + IETF code is subject to the following copyright and license: + Copyright (c) IETF Trust and the persons identified as authors of + the code. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in + Section 4.c of the IETF Trust's Legal Provisions Relating + to IETF Documents (http://trustee.ietf.org/license-info)."; + + oc-ext:openconfig-version "0.2.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.2.1"; + } + + revision "2018-01-16" { + description + "Moved alarm identities into separate types module"; + reference "0.2.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + identity OPENCONFIG_ALARM_TYPE_ID { + description + "Base identity for alarm type ID profiles"; + } + + identity AIS { + base OPENCONFIG_ALARM_TYPE_ID; + description + "Defines an alarm indication signal type of alarm"; + } + + identity EQPT { + base OPENCONFIG_ALARM_TYPE_ID; + description + "Defines an equipment related type of alarm that is specific + to the physical hardware"; + } + + identity LOS { + base OPENCONFIG_ALARM_TYPE_ID; + description + "Defines a loss of signal type of alarm"; + } + + identity OTS { + base OPENCONFIG_ALARM_TYPE_ID; + description + "Defines a optical transport signal type of alarm"; + } + + identity OPENCONFIG_ALARM_SEVERITY { + description + "Base identity for alarm severity profiles. Derived + identities are based on contents of the draft + IETF YANG Alarm Module"; + reference + "IETF YANG Alarm Module: Draft - typedef severity + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02"; + + } + + identity UNKNOWN { + base OPENCONFIG_ALARM_SEVERITY; + description + "Indicates that the severity level could not be determined. + This level SHOULD be avoided."; + } + + identity MINOR { + base OPENCONFIG_ALARM_SEVERITY; + description + "Indicates the existence of a non-service affecting fault + condition and that corrective action should be taken in + order to prevent a more serious (for example, service + affecting) fault. Such a severity can be reported, for + example, when the detected alarm condition is not currently + degrading the capacity of the resource"; + } + + identity WARNING { + base OPENCONFIG_ALARM_SEVERITY; + description + "Indicates the detection of a potential or impending service + affecting fault, before any significant effects have been felt. + Action should be taken to further diagnose (if necessary) and + correct the problem in order to prevent it from becoming a more + serious service affecting fault."; + } + + identity MAJOR { + base OPENCONFIG_ALARM_SEVERITY; + description + "Indicates that a service affecting condition has developed + and an urgent corrective action is required. Such a severity + can be reported, for example, when there is a severe + degradation in the capability of the resource and its full + capability must be restored."; + } + + identity CRITICAL { + base OPENCONFIG_ALARM_SEVERITY; + description + "Indicates that a service affecting condition has occurred + and an immediate corrective action is required. Such a + severity can be reported, for example, when a resource becomes + totally out of service and its capability must be restored."; + } + +} \ No newline at end of file diff --git a/models/yang/common/openconfig-alarms.yang b/models/yang/common/openconfig-alarms.yang new file mode 100644 index 000000000..5bcc56314 --- /dev/null +++ b/models/yang/common/openconfig-alarms.yang @@ -0,0 +1,231 @@ +module openconfig-alarms { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/alarms"; + + prefix "oc-alarms"; + + // import some basic types + import openconfig-alarm-types { prefix oc-alarm-types; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-types { prefix oc-types; } + import openconfig-platform { prefix oc-platform; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines operational state data related to alarms + that the device is reporting. + + This model reuses some data items defined in the draft IETF + YANG Alarm Module: + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02 + + Portions of this code were derived from the draft IETF YANG Alarm + Module. Please reproduce this note if possible. + + IETF code is subject to the following copyright and license: + Copyright (c) IETF Trust and the persons identified as authors of + the code. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in + Section 4.c of the IETF Trust's Legal Provisions Relating + to IETF Documents (http://trustee.ietf.org/license-info)."; + + oc-ext:openconfig-version "0.3.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.3.1"; + } + + revision "2018-01-16" { + description + "Moved alarm identities into separate types module"; + reference "0.3.0"; + } + + revision "2018-01-10" { + description + "Make alarms list read only"; + reference "0.2.0"; + } + + revision "2017-08-24" { + description + "Initial public release"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // grouping statements + + grouping alarm-state { + description + "Operational state data for device alarms"; + + leaf id { + type string; + description + "Unique ID for the alarm -- this will not be a + configurable parameter on many implementations"; + } + + leaf resource { + type string; + description + "The item that is under alarm within the device. The + resource may be a reference to an item which is + defined elsewhere in the model. For example, it + may be a platform/component, interfaces/interface, + terminal-device/logical-channels/channel, etc. In this + case the system should match the name of the referenced + item exactly. The referenced item could alternatively be + the path of the item within the model."; + reference + "IETF YANG Alarm Module: Draft - typedef resource + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02"; + } + + leaf text { + type string; + description + "The string used to inform operators about the alarm. This + MUST contain enough information for an operator to be able + to understand the problem. If this string contains structure, + this format should be clearly documented for programs to be + able to parse that information"; + reference + "IETF YANG Alarm Module: Draft - typedef alarm-text + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02"; + } + + leaf time-created { + type oc-types:timeticks64; + description + "The time at which the alarm was raised by the system. + This value is expressed as nanoseconds since the Unix Epoch"; + } + + leaf severity { + type identityref { + base oc-alarm-types:OPENCONFIG_ALARM_SEVERITY; + } + description + "The severity level indicating the criticality and impact + of the alarm"; + reference + "IETF YANG Alarm Module: Draft - typedef severity + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02"; + } + + leaf type-id { + type union { + type string; + type identityref { + base oc-alarm-types:OPENCONFIG_ALARM_TYPE_ID; + } + } + description + "The abbreviated name of the alarm, for example LOS, + EQPT, or OTS. Also referred to in different systems as + condition type, alarm identifier, or alarm mnemonic. It + is recommended to use the OPENCONFIG_ALARM_TYPE_ID + identities where possible and only use the string type + when the desired identityref is not yet defined"; + reference + "IETF YANG Alarm Module: Draft - typedef alarm-type-id + https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02"; + } + } + + grouping alarm-config { + description + "Configuration data for device alarms"; + } + + grouping alarms-top { + description + "Top-level grouping for device alarms"; + + container alarms { + description + "Top-level container for device alarms"; + + config false; + + list alarm { + key "id"; + description + "List of alarms, keyed by a unique id"; + + leaf id { + type leafref { + path "../state/id"; + } + + description + "References the unique alarm id"; + } + + container config { + description + "Configuration data for each alarm"; + + uses alarm-config; + } + + container state { + config false; + + description + "Operational state data for a device alarm"; + + uses alarm-config; + uses alarm-state; + } + } + } + } + + + // augments + + augment "/oc-platform:components/oc-platform:component/oc-platform:state" { + description + "Adds specific alarms related to a component."; + + leaf equipment-failure { + type boolean; + default "false"; + description + "If true, the hardware indicates that the component's physical equipment + has failed"; + } + + leaf equipment-mismatch { + type boolean; + default "false"; + description + "If true, the hardware indicates that the component inserted into the + affected component's physical location is of a different type than what + is configured"; + } + } + +} diff --git a/models/yang/common/openconfig-extensions.yang b/models/yang/common/openconfig-extensions.yang new file mode 100644 index 000000000..361e55426 --- /dev/null +++ b/models/yang/common/openconfig-extensions.yang @@ -0,0 +1,175 @@ +module openconfig-extensions { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/openconfig-ext"; + + prefix "oc-ext"; + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module provides extensions to the YANG language to allow + OpenConfig specific functionality and meta-data to be defined."; + + revision "2018-10-17" { + description + "Add extension for regular expression type."; + reference "0.4.0"; + } + + revision "2017-04-11" { + description + "rename password type to 'hashed' and clarify description"; + reference "0.3.0"; + } + + revision "2017-01-29" { + description + "Added extension for annotating encrypted values."; + reference "0.2.0"; + } + + revision "2015-10-09" { + description + "Initial OpenConfig public release"; + reference "0.1.0"; + } + + + // extension statements + extension openconfig-version { + argument "semver" { + yin-element false; + } + description + "The OpenConfig version number for the module. This is + expressed as a semantic version number of the form: + x.y.z + where: + * x corresponds to the major version, + * y corresponds to a minor version, + * z corresponds to a patch version. + This version corresponds to the model file within which it is + defined, and does not cover the whole set of OpenConfig models. + Where several modules are used to build up a single block of + functionality, the same module version is specified across each + file that makes up the module. + + A major version number of 0 indicates that this model is still + in development (whether within OpenConfig or with industry + partners), and is potentially subject to change. + + Following a release of major version 1, all modules will + increment major revision number where backwards incompatible + changes to the model are made. + + The minor version is changed when features are added to the + model that do not impact current clients use of the model. + + The patch-level version is incremented when non-feature changes + (such as bugfixes or clarifications to human-readable + descriptions that do not impact model functionality) are made + that maintain backwards compatibility. + + The version number is stored in the module meta-data."; + } + + extension openconfig-hashed-value { + description + "This extension provides an annotation on schema nodes to + indicate that the corresponding value should be stored and + reported in hashed form. + + Hash algorithms are by definition not reversible. Clients + reading the configuration or applied configuration for the node + should expect to receive only the hashed value. Values written + in cleartext will be hashed. This annotation may be used on + nodes such as secure passwords in which the device never reports + a cleartext value, even if the input is provided as cleartext."; + } + + extension regexp-posix { + description + "This extension indicates that the regular expressions included + within the YANG module specified are conformant with the POSIX + regular expression format rather than the W3C standard that is + specified by RFC6020 and RFC7950."; + } + + extension telemetry-on-change { + description + "The telemetry-on-change annotation is specified in the context + of a particular subtree (container, or list) or leaf within the + YANG schema. Where specified, it indicates that the value stored + by the nodes within the context change their value only in response + to an event occurring. The event may be local to the target, for + example - a configuration change, or external - such as the failure + of a link. + + When a telemetry subscription allows the target to determine whether + to export the value of a leaf in a periodic or event-based fashion + (e.g., TARGET_DEFINED mode in gNMI), leaves marked as + telemetry-on-change should only be exported when they change, + i.e., event-based."; + } + + extension telemetry-atomic { + description + "The telemetry-atomic annotation is specified in the context of + a subtree (containre, or list), and indicates that all nodes + within the subtree are always updated together within the data + model. For example, all elements under the subtree may be updated + as a result of a new alarm being raised, or the arrival of a new + protocol message. + + Transport protocols may use the atomic specification to determine + optimisations for sending or storing the corresponding data."; + } + + extension operational { + description + "The operational annotation is specified in the context of a + grouping, leaf, or leaf-list within a YANG module. It indicates + that the nodes within the context are derived state on the device. + + OpenConfig data models divide nodes into the following three categories: + + - intended configuration - these are leaves within a container named + 'config', and are the writable configuration of a target. + - applied configuration - these are leaves within a container named + 'state' and are the currently running value of the intended configuration. + - derived state - these are the values within the 'state' container which + are not part of the applied configuration of the device. Typically, they + represent state values reflecting underlying operational counters, or + protocol statuses."; + } + + extension catalog-organization { + argument "org" { + yin-element false; + } + description + "This extension specifies the organization name that should be used within + the module catalogue on the device for the specified YANG module. It stores + a pithy string where the YANG organization statement may contain more + details."; + } + + extension origin { + argument "origin" { + yin-element false; + } + description + "This extension specifies the name of the origin that the YANG module + falls within. This allows multiple overlapping schema trees to be used + on a single network element without requiring module based prefixing + of paths."; + } +} diff --git a/models/yang/common/openconfig-if-aggregate.yang b/models/yang/common/openconfig-if-aggregate.yang new file mode 100644 index 000000000..a8f18f7f8 --- /dev/null +++ b/models/yang/common/openconfig-if-aggregate.yang @@ -0,0 +1,232 @@ +module openconfig-if-aggregate { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces/aggregate"; + + prefix "oc-lag"; + + // import some basic types + import openconfig-interfaces { prefix oc-if; } + import openconfig-if-ethernet { prefix oc-eth; } + import iana-if-type { prefix ift; } + import openconfig-if-types { prefix oc-ift; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "Model for managing aggregated (aka bundle, LAG) interfaces."; + + oc-ext:openconfig-version "2.3.2"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "2.3.2"; + } + + revision "2018-03-23" { + description + "Fix/cleanup when statements in aggregates model."; + reference "2.3.1"; + } + + revision "2018-01-05" { + description + "Add logical loopback to interface."; + reference "2.3.0"; + } + + revision "2017-12-22" { + description + "Add IPv4 proxy ARP configuration."; + reference "2.2.0"; + } + + revision "2017-12-21" { + description + "Added IPv6 router advertisement configuration."; + reference "2.1.0"; + } + + revision "2017-07-14" { + description + "Added Ethernet/IP state data; Add dhcp-client; + migrate to OpenConfig types modules; Removed or + renamed opstate values"; + reference "2.0.0"; + } + + revision "2016-12-22" { + description + "Fixes to Ethernet interfaces model"; + reference "1.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // extension statements + + // feature statements + + // identity statements + + // typedef statements + + typedef aggregation-type { + type enumeration { + enum LACP { + description "LAG managed by LACP"; + } + enum STATIC { + description "Statically configured bundle / LAG"; + } + } + description + "Type to define the lag-type, i.e., how the LAG is + defined and managed"; + } + + // grouping statements + + + grouping aggregation-logical-config { + description + "Configuration data for aggregate interfaces"; + + + leaf lag-type { + type aggregation-type; + description + "Sets the type of LAG, i.e., how it is + configured / maintained"; + } + + leaf min-links { + type uint16; + description + "Specifies the mininum number of member + interfaces that must be active for the aggregate interface + to be available"; + } + } + + grouping aggregation-logical-state { + description + "Operational state data for aggregate interfaces"; + + leaf lag-speed { + type uint32; + units Mbps; + description + "Reports effective speed of the aggregate interface, + based on speed of active member interfaces"; + } + + leaf-list member { + when "../../config/lag-type = 'STATIC'" { + description + "The simple list of member interfaces is active + when the aggregate is statically configured"; + } + type oc-if:base-interface-ref; + description + "List of current member interfaces for the aggregate, + expressed as references to existing interfaces"; + } + } + + grouping aggregation-logical-top { + description "Top-level data definitions for LAGs"; + + container aggregation { + + description + "Options for logical interfaces representing + aggregates"; + + container config { + description + "Configuration variables for logical aggregate / + LAG interfaces"; + + uses aggregation-logical-config; + } + + container state { + + config false; + description + "Operational state variables for logical + aggregate / LAG interfaces"; + + uses aggregation-logical-config; + uses aggregation-logical-state; + + } + } + } + + grouping ethernet-if-aggregation-config { + description + "Adds configuration items for Ethernet interfaces + belonging to a logical aggregate / LAG"; + + leaf aggregate-id { + type leafref { + path "/oc-if:interfaces/oc-if:interface/oc-if:name"; + } + description + "Specify the logical aggregate interface to which + this interface belongs"; + } + } + + // data definition statements + + // augment statements + + augment "/oc-if:interfaces/oc-if:interface" { + + description "Adds LAG configuration to the interface module"; + + uses aggregation-logical-top { + when "oc-if:state/oc-if:type = 'ift:ieee8023adLag' or " + + "oc-if:state/oc-if:type = 'oc-ift:IF_AGGREGATE'" { + description + "active when the interface is set to type LAG"; + } + } + } + + augment "/oc-if:interfaces/oc-if:interface/oc-eth:ethernet/" + + "oc-eth:config" { + description + "Adds LAG settings to individual Ethernet interfaces"; + + uses ethernet-if-aggregation-config; + } + + augment "/oc-if:interfaces/oc-if:interface/oc-eth:ethernet/" + + "oc-eth:state" { + description + "Adds LAG settings to individual Ethernet interfaces"; + + uses ethernet-if-aggregation-config; + } + + // rpc statements + + // notification statements + +} diff --git a/models/yang/common/openconfig-if-ethernet-ext.yang b/models/yang/common/openconfig-if-ethernet-ext.yang new file mode 100644 index 000000000..f64773b22 --- /dev/null +++ b/models/yang/common/openconfig-if-ethernet-ext.yang @@ -0,0 +1,117 @@ +module openconfig-if-ethernet-ext { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces/ethernet-ext"; + + prefix "oc-eth-ext"; + + // import some basic types + import openconfig-interfaces { prefix oc-if; } + import openconfig-if-ethernet { prefix oc-eth; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module adds extensions to the base ethernet configuration + and operational state model to support additional use cases."; + + oc-ext:openconfig-version "0.1.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.1.1"; + } + + revision "2018-07-10" { + description + "Initial version of Ethernet extensions module to add frame + size distribution stats"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + grouping ethernet-in-frames-size-dist { + description + "Grouping for defining the size distribution of the frames + received"; + + container in-distribution { + description + "The size distribution of the received frames."; + + leaf in-frames-64-octets { + type oc-yang:counter64; + description + "Number of packets (including bad packets) received that + were 64 bytes in length (excluding framing bits but + including FCS bytes)."; + } + + leaf in-frames-65-127-octets { + type oc-yang:counter64; + description + "Number of good and bad packets received that were + between 65 and 127 bytes in length (excluding framing bits + but including FCS bytes)."; + } + + leaf in-frames-128-255-octets { + type oc-yang:counter64; + description + "Number of good and bad packets received that were + between 128 and 255 bytes in length inclusive + (excluding framing bits but including FCS bytes)."; + } + + leaf in-frames-256-511-octets { + type oc-yang:counter64; + description + "Number of good and bad packets received that were + between 256 and 511 bytes in length inclusive + (excluding framing bits but including FCS bytes)."; + } + + leaf in-frames-512-1023-octets { + type oc-yang:counter64; + description + "Number of good and bad packets received that were + between 512 and 1023 bytes in length inclusive + (excluding framing bits but including FCS bytes)."; + } + + leaf in-frames-1024-1518-octets { + type oc-yang:counter64; + description + "Number of good and bad packets received that were + between 1024 and 1518 bytes in length inclusive + (excluding framing bits but including FCS bytes)."; + } + } + } + + // augment statements + + augment "/oc-if:interfaces/oc-if:interface/oc-eth:ethernet/" + + "oc-eth:state/oc-eth:counters" { + description + "Adds size distribution to the ethernet counters"; + + uses ethernet-in-frames-size-dist; + } + +} \ No newline at end of file diff --git a/models/yang/common/openconfig-if-ip-ext.yang b/models/yang/common/openconfig-if-ip-ext.yang new file mode 100644 index 000000000..2f2893497 --- /dev/null +++ b/models/yang/common/openconfig-if-ip-ext.yang @@ -0,0 +1,179 @@ +module openconfig-if-ip-ext { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces/ip-ext"; + + prefix "oc-ip-ext"; + + import openconfig-interfaces { prefix oc-if; } + import openconfig-if-ip { prefix oc-ip; } + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module adds extensions to the base IP configuration and + operational state model to support additional use cases."; + + oc-ext:openconfig-version "2.3.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "2.3.1"; + } + + revision "2018-01-05" { + description + "Add logical loopback to interface."; + reference "2.3.0"; + } + + revision "2017-12-21" { + description + "Added IPv6 router advertisement configuration."; + reference "2.1.0"; + } + + revision "2017-07-14" { + description + "Added Ethernet/IP state data; Add dhcp-client; + migrate to OpenConfig types modules; Removed or + renamed opstate values"; + reference "2.0.0"; + } + + revision "2016-12-22" { + description + "Fixes to Ethernet interfaces model"; + reference "1.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // grouping statements + + grouping ipv6-autoconf-config { + description + "Configuration data for IPv6 address autoconfiguration"; + + leaf create-global-addresses { + type boolean; + default true; + description + "[adapted from IETF IP model RFC 7277] + + If enabled, the host creates global addresses as + described in RFC 4862."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration + Section 5.5"; + } + leaf create-temporary-addresses { + type boolean; + default false; + description + "[adapted from IETF IP model RFC 7277] + + If enabled, the host creates temporary addresses as + described in RFC 4941."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6"; + } + + leaf temporary-valid-lifetime { + type uint32; + units "seconds"; + default 604800; + description + "[adapted from IETF IP model RFC 7277] + + The time period during which the temporary address + is valid."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + - TEMP_VALID_LIFETIME"; + } + + leaf temporary-preferred-lifetime { + type uint32; + units "seconds"; + default 86400; + description + "[adapted from IETF IP model RFC 7277] + + The time period during which the temporary address is + preferred."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + - TEMP_PREFERRED_LIFETIME"; + } + } + + grouping ipv6-autoconf-state { + description + "Operational state data for IPv6 address autoconfiguration"; + + //TODO: placeholder for additional opstate for IPv6 autoconf + } + + grouping ipv6-autoconf-top { + description + "Top-level grouping for IPv6 address autoconfiguration"; + + container autoconf { + description + "Top-level container for IPv6 autoconf"; + + container config { + description + "[adapted from IETF IP model RFC 7277] + + Parameters to control the autoconfiguration of IPv6 + addresses, as described in RFC 4862."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration"; + + uses ipv6-autoconf-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses ipv6-autoconf-config; + uses ipv6-autoconf-state; + } + } + } + + // data definition statements + + // augment statements + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface/oc-ip:ipv6" { + description + "Adds address autoconfiguration to the base IP model"; + + uses ipv6-autoconf-top; + } + +} diff --git a/models/yang/common/openconfig-if-poe.yang b/models/yang/common/openconfig-if-poe.yang new file mode 100644 index 000000000..7758ea318 --- /dev/null +++ b/models/yang/common/openconfig-if-poe.yang @@ -0,0 +1,110 @@ +module openconfig-if-poe { + + yang-version "1"; + + namespace "http://openconfig.net/yang/poe"; + + prefix "oc-poe"; + + import openconfig-if-ethernet { prefix oc-eth; } + import openconfig-interfaces { prefix oc-if; } + import openconfig-extensions { prefix oc-ext; } + + organization "OpenConfig working group"; + + contact + "Openconfig working group + www.openconfig.net"; + + description + "This module defines configuration and state data for + Power over Ethernet (PoE) based on the IEEE 802.3af + standard."; + + oc-ext:openconfig-version "0.1.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.1.1"; + } + + revision "2017-09-14" { + description + "Initial public revision"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + grouping poe-ethernet-config { + description + "PoE ethernet config grouping"; + + leaf enabled { + type boolean; + default "true"; + description + "Enable or disable PoE in the ethernet interface."; + } + } + + grouping poe-ethernet-state { + description + "PoE ethernet state grouping"; + + leaf power-used { + type decimal64 { + fraction-digits 2; + } + units Watts; + description + "Power used by the ethernet interface in Watts."; + } + + leaf power-class { + type uint8; + description + "IEEE 802.3af Power class detected for this ethernet + interface."; + } + } + + grouping poe-ethernet-top { + description + "Ethernet top level grouping"; + + container poe { + description + "Top-level container for PoE configuration and state data"; + + container config { + description + "Configuration data for PoE"; + + uses poe-ethernet-config; + } + + container state { + config false; + + description + "Operational state data for PoE"; + + uses poe-ethernet-config; + uses poe-ethernet-state; + } + } + } + + augment "/oc-if:interfaces/oc-if:interface/oc-eth:ethernet" { + description + "Adds PoE to the ethernet model."; + + uses poe-ethernet-top; + } + +} diff --git a/models/yang/common/openconfig-if-tunnel.yang b/models/yang/common/openconfig-if-tunnel.yang new file mode 100644 index 000000000..3003699d5 --- /dev/null +++ b/models/yang/common/openconfig-if-tunnel.yang @@ -0,0 +1,120 @@ +module openconfig-if-tunnel { + yang-version "1"; + + namespace "http://openconfig.net/yang/interfaces/tunnel"; + + prefix "oc-tun"; + + import openconfig-interfaces { prefix oc-if; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-inet-types { prefix oc-inet; } + import openconfig-if-ip { prefix oc-ip; } + + organization + "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "This model adds extensions to the OpenConfig interfaces + model to configure tunnel interfaces on a network + device."; + + oc-ext:openconfig-version "0.1.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.1.1"; + } + + revision "2018-01-05" { + description + "Initial tunnel model"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + grouping tunnel-top { + description + "Top-level grouping for parameters related to + a tunnel interface."; + + container tunnel { + description + "In the case that the interface is logical tunnel + interface, the parameters for the tunnel are + specified within this subtree. Tunnel interfaces + have only a single logical subinterface associated + with them."; + + container config { + description + "Configuration parameters associated with the + tunnel interface"; + uses tunnel-config; + } + + container state { + config false; + description + "Operational state parameters associated with + the tunnel interface."; + uses tunnel-config; + } + + uses oc-ip:ipv4-top; + uses oc-ip:ipv6-top; + } + } + + grouping tunnel-config { + description + "Configuraton parameters relating to a tunnel + interface."; + + leaf src { + type oc-inet:ip-address; + description + "The source address that should be used for the + tunnel."; + } + + leaf dst { + type oc-inet:ip-address; + description + "The destination address for the tunnel."; + } + + leaf ttl { + type uint8 { + range "1..255"; + } + description + "The time-to-live (or hop limit) that should be utilised + for the IP packets used for the tunnel transport."; + } + + leaf gre-key { + type uint32; + description + "The GRE key to be specified for the tunnel. The + key is used to identify a traffic flow within + a tunnel."; + reference + "RFC2890: Key and Sequence Number Extensions to GRE"; + } + } + + augment "/oc-if:interfaces/oc-if:interface" { + description + "Augment to add tunnel configuration to interfaces"; + uses tunnel-top; + } +} diff --git a/models/yang/common/openconfig-if-types.yang b/models/yang/common/openconfig-if-types.yang new file mode 100644 index 000000000..27d2dc1d8 --- /dev/null +++ b/models/yang/common/openconfig-if-types.yang @@ -0,0 +1,108 @@ +module openconfig-if-types { + yang-version "1"; + + namespace "http://openconfig.net/yang/openconfig-if-types"; + + prefix "oc-ift"; + + // import statements + import openconfig-extensions { prefix oc-ext; } + + // meta + organization + "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "This module contains a set of interface type definitions that + are used across OpenConfig models. These are generally physical + or logical interfaces, distinct from hardware ports (which are + described by the OpenConfig platform model)."; + + oc-ext:openconfig-version "0.2.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.2.1"; + } + + revision "2018-01-05" { + description + "Add tunnel types into the INTERFACE_TYPE identity."; + reference "0.2.0"; + } + + revision "2016-11-14" { + description + "Initial version"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + identity INTERFACE_TYPE { + description + "Base identity from which interface types are derived."; + } + + identity IF_ETHERNET { + base INTERFACE_TYPE; + description + "Ethernet interfaces based on IEEE 802.3 standards, as well + as FlexEthernet"; + reference + "IEEE 802.3-2015 - IEEE Standard for Ethernet + OIF Flex Ethernet Implementation Agreement 1.0"; + } + + identity IF_AGGREGATE { + base INTERFACE_TYPE; + description + "An aggregated, or bonded, interface forming a + Link Aggregation Group (LAG), or bundle, most often based on + the IEEE 802.1AX (or 802.3ad) standard."; + reference + "IEEE 802.1AX-2008"; + } + + identity IF_LOOPBACK { + base INTERFACE_TYPE; + description + "A virtual interface designated as a loopback used for + various management and operations tasks."; + } + + identity IF_ROUTED_VLAN { + base INTERFACE_TYPE; + description + "A logical interface used for routing services on a VLAN. + Such interfaces are also known as switch virtual interfaces + (SVI) or integrated routing and bridging interfaces (IRBs)."; + } + + identity IF_SONET { + base INTERFACE_TYPE; + description + "SONET/SDH interface"; + } + + identity IF_TUNNEL_GRE4 { + base INTERFACE_TYPE; + description + "A GRE tunnel over IPv4 transport."; + } + + identity IF_TUNNEL_GRE6 { + base INTERFACE_TYPE; + description + "A GRE tunnel over IPv6 transport."; + } + +} diff --git a/models/yang/common/openconfig-inet-types.yang b/models/yang/common/openconfig-inet-types.yang new file mode 100644 index 000000000..7c23d2b38 --- /dev/null +++ b/models/yang/common/openconfig-inet-types.yang @@ -0,0 +1,343 @@ +module openconfig-inet-types { + + yang-version "1"; + namespace "http://openconfig.net/yang/types/inet"; + prefix "oc-inet"; + + import openconfig-extensions { prefix "oc-ext"; } + + organization + "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module contains a set of Internet address related + types for use in OpenConfig modules. + + Portions of this code were derived from IETF RFC 6021. + Please reproduce this note if possible. + + IETF code is subject to the following copyright and license: + Copyright (c) IETF Trust and the persons identified as authors of + the code. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in + Section 4.c of the IETF Trust's Legal Provisions Relating + to IETF Documents (http://trustee.ietf.org/license-info)."; + + oc-ext:openconfig-version "0.3.2"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.3.2"; + } + + revision 2017-08-24 { + description + "Minor formatting fixes."; + reference "0.3.1"; + } + + revision 2017-07-06 { + description + "Add domain-name and host typedefs"; + reference "0.3.0"; + } + + revision 2017-04-03 { + description + "Add ip-version typedef."; + reference "0.2.0"; + } + + revision 2017-04-03 { + description + "Update copyright notice."; + reference "0.1.1"; + } + + revision 2017-01-26 { + description + "Initial module for inet types"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // IPv4 and IPv6 types. + + typedef ipv4-address { + type string { + pattern '^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|' + + '25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4]' + + '[0-9]|25[0-5])$'; + } + description + "An IPv4 address in dotted quad notation using the default + zone."; + } + + typedef ipv4-address-zoned { + type string { + pattern '^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|' + + '25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4]' + + '[0-9]|25[0-5])(%[a-zA-Z0-9_]+)$'; + } + description + "An IPv4 address in dotted quad notation. This type allows + specification of a zone index to disambiguate identical + address values. For link-local addresses, the index is + typically the interface index or interface name."; + } + + typedef ipv6-address { + type string { + pattern + // Must support compression through different lengths + // therefore this regexp is complex. + '^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|' + + '([0-9a-fA-F]{1,4}:){1,7}:|' + + '([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|' + + '([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|' + + '([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|' + + '([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|' + + '([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|' + + '[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|' + + ':((:[0-9a-fA-F]{1,4}){1,7}|:)' + + ')$'; + } + description + "An IPv6 address represented as either a full address; shortened + or mixed-shortened formats, using the default zone."; + } + + typedef ipv6-address-zoned { + type string { + pattern + // Must support compression through different lengths + // therefore this regexp is complex. + '^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|' + + '([0-9a-fA-F]{1,4}:){1,7}:|' + + '([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|' + + '([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|' + + '([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|' + + '([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|' + + '([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|' + + '[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|' + + ':((:[0-9a-fA-F]{1,4}){1,7}|:)' + + ')(%[a-zA-Z0-9_]+)$'; + } + description + "An IPv6 address represented as either a full address; shortened + or mixed-shortened formats. This type allows specification of + a zone index to disambiguate identical address values. For + link-local addresses, the index is typically the interface + index or interface name."; + } + + typedef ipv4-prefix { + type string { + pattern '^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|' + + '25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4]' + + '[0-9]|25[0-5])/(([0-9])|([1-2][0-9])|(3[0-2]))$'; + } + description + "An IPv4 prefix represented in dotted quad notation followed by + a slash and a CIDR mask (0 <= mask <= 32)."; + } + + typedef ipv6-prefix { + type string { + pattern + '^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|' + + '([0-9a-fA-F]{1,4}:){1,7}:|' + + '([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}' + + '([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|' + + '([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|' + + '([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|' + + '([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|' + + '[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|' + + ':((:[0-9a-fA-F]{1,4}){1,7}|:)' + + ')/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9])$'; + } + description + "An IPv6 prefix represented in full, shortened, or mixed + shortened format followed by a slash and CIDR mask + (0 <= mask <= 128)."; + } + + typedef ip-address { + type union { + type ipv4-address; + type ipv6-address; + } + description + "An IPv4 or IPv6 address with no prefix specified."; + } + + typedef ip-prefix { + type union { + type ipv4-prefix; + type ipv6-prefix; + } + description + "An IPv4 or IPv6 prefix."; + } + + typedef ip-version { + type enumeration { + enum UNKNOWN { + value 0; + description + "An unknown or unspecified version of the Internet + protocol."; + } + enum IPV4 { + value 4; + description + "The IPv4 protocol as defined in RFC 791."; + } + enum IPV6 { + value 6; + description + "The IPv6 protocol as defined in RFC 2460."; + } + } + description + "This value represents the version of the IP protocol. + Note that integer representation of the enumerated values + are not specified, and are not required to follow the + InetVersion textual convention in SMIv2."; + reference + "RFC 791: Internet Protocol + RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + typedef domain-name { + type string { + length "1..253"; + pattern + '((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*' + + '([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)' + + '|\.'; + } + description + "The domain-name type represents a DNS domain name. + Fully quallified left to the models which utilize this type. + + Internet domain names are only loosely specified. Section + 3.5 of RFC 1034 recommends a syntax (modified in Section + 2.1 of RFC 1123). The pattern above is intended to allow + for current practice in domain name use, and some possible + future expansion. It is designed to hold various types of + domain names, including names used for A or AAAA records + (host names) and other records, such as SRV records. Note + that Internet host names have a stricter syntax (described + in RFC 952) than the DNS recommendations in RFCs 1034 and + 1123, and that systems that want to store host names in + schema nodes using the domain-name type are recommended to + adhere to this stricter standard to ensure interoperability. + + The encoding of DNS names in the DNS protocol is limited + to 255 characters. Since the encoding consists of labels + prefixed by a length bytes and there is a trailing NULL + byte, only 253 characters can appear in the textual dotted + notation. + + Domain-name values use the US-ASCII encoding. Their canonical + format uses lowercase US-ASCII characters. Internationalized + domain names MUST be encoded in punycode as described in RFC + 3492"; + } + + typedef host { + type union { + type ip-address; + type domain-name; + } + description + "The host type represents either an unzoned IP address or a DNS + domain name."; + } + + typedef as-number { + type uint32; + description + "A numeric identifier for an autonomous system (AS). An AS is a + single domain, under common administrative control, which forms + a unit of routing policy. Autonomous systems can be assigned a + 2-byte identifier, or a 4-byte identifier which may have public + or private scope. Private ASNs are assigned from dedicated + ranges. Public ASNs are assigned from ranges allocated by IANA + to the regional internet registries (RIRs)."; + reference + "RFC 1930 Guidelines for creation, selection, and registration + of an Autonomous System (AS) + RFC 4271 A Border Gateway Protocol 4 (BGP-4)"; + } + + typedef dscp { + type uint8 { + range "0..63"; + } + description + "A differentiated services code point (DSCP) marking within the + IP header."; + reference + "RFC 2474 Definition of the Differentiated Services Field + (DS Field) in the IPv4 and IPv6 Headers"; + } + + typedef ipv6-flow-label { + type uint32 { + range "0..1048575"; + } + description + "The IPv6 flow-label is a 20-bit value within the IPv6 header + which is optionally used by the source of the IPv6 packet to + label sets of packets for which special handling may be + required."; + reference + "RFC 2460 Internet Protocol, Version 6 (IPv6) Specification"; + } + + typedef port-number { + type uint16; + description + "A 16-bit port number used by a transport protocol such as TCP + or UDP."; + reference + "RFC 768 User Datagram Protocol + RFC 793 Transmission Control Protocol"; + } + + typedef uri { + type string; + description + "An ASCII-encoded Uniform Resource Identifier (URI) as defined + in RFC 3986."; + reference + "RFC 3986 Uniform Resource Identifier (URI): Generic Syntax"; + } + + typedef url { + type string; + description + "An ASCII-encoded Uniform Resource Locator (URL) as defined + in RFC 3986, section 1.1.3"; + reference + "RFC 3986, paragraph 1.1.3"; + } + +} diff --git a/models/yang/common/openconfig-lldp-types.yang b/models/yang/common/openconfig-lldp-types.yang new file mode 100644 index 000000000..6c4a0ac17 --- /dev/null +++ b/models/yang/common/openconfig-lldp-types.yang @@ -0,0 +1,306 @@ +module openconfig-lldp-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/lldp/types"; + + prefix "oc-lldp-types"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines types related to the LLDP protocol model."; + + oc-ext:openconfig-version "0.1.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.1.1"; + } + + revision "2016-05-16" { + description + "Initial public revision"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + + identity LLDP_SYSTEM_CAPABILITY { + description + "Base identity for standard LLDP system capabilities. + The system capabilities field contains a bit-map of the + capabilities that define the primary function(s) of + the system. A system may advertise more than one capability."; + reference + "Table 8-4 System Capabilities, IEEE 802.1AB-2009"; + } + + identity OTHER { + base LLDP_SYSTEM_CAPABILITY; + description + "Other capability not specified; bit position 1"; + } + + identity REPEATER { + base LLDP_SYSTEM_CAPABILITY; + description + "Repeater capability; bit position 2"; + reference + "IETF RFC 2108"; + } + + identity MAC_BRIDGE { + base LLDP_SYSTEM_CAPABILITY; + description + "MAC bridge capability; bit position 3"; + reference + "IEEE Std 802.1D"; + } + + identity WLAN_ACCESS_POINT { + base LLDP_SYSTEM_CAPABILITY; + description + "WLAN access point capability; bit position 4"; + reference + "IEEE Std 802.11 MIB"; + } + + identity ROUTER { + base LLDP_SYSTEM_CAPABILITY; + description + "Router; bit position 5"; + reference + "IETF RFC 1812"; + } + + identity TELEPHONE { + base LLDP_SYSTEM_CAPABILITY; + description + "Telephone capability; bit position 6"; + reference + "IETF RFC 4293"; + } + + identity DOCSIS_CABLE_DEVICE { + base LLDP_SYSTEM_CAPABILITY; + description + "DOCSIS cable device; bit position 7"; + reference + "IETF RFC 4639 and IETF RFC 4546"; + } + + identity STATION_ONLY { + base LLDP_SYSTEM_CAPABILITY; + description + "Station only capability, for devices that implement only an + end station capability, and for which none of the other + capabilities apply; bit position 8"; + reference + "IETF RFC 4293"; + } + + identity C_VLAN { + base LLDP_SYSTEM_CAPABILITY; + description + "C-VLAN component of a VLAN Bridge; bit position 9"; + reference + "IEEE Std 802.1Q"; + } + + identity S_VLAN { + base LLDP_SYSTEM_CAPABILITY; + description + "S-VLAN component of a VLAN Bridge; bit position 10"; + reference + "IEEE Std 802.1Q"; + } + + identity TWO_PORT_MAC_RELAY { + base LLDP_SYSTEM_CAPABILITY; + description + "Two-port MAC Relay (TPMR) capability; bit position 11"; + reference + "IEEE Std 802.1Q"; + } + + identity LLDP_TLV { + description + "A base identity which describes the TLVs in LLDP"; + } + + identity CHASSIS_ID { + base LLDP_TLV; + description + "The chassis identifier of the device associated with + the transmitting LLDP agent"; + reference "IEEE Std 802.1AB"; + } + + identity PORT_ID { + base LLDP_TLV; + description + "The port identifier associated with the interface + on with the LLDP agent is transmitting"; + reference "IEEE Std 802.1AB"; + } + + identity PORT_DESCRIPTION { + base LLDP_TLV; + description + "The description of the port that is associated with + the interface on which the LLDP agent is transmitting"; + reference "IEEE Std 802.1AB"; + } + + identity SYSTEM_NAME { + base LLDP_TLV; + description + "The assigned name (sysName or hostname) of the device + which is transmitting the LLDP PDU"; + reference "IEEE Std 802.1AB"; + } + + identity SYSTEM_DESCRIPTION { + base LLDP_TLV; + description + "The description (sysDescr) of the device which is + transmitting the LLDP PDU"; + reference "IEEE Std 802.1AB"; + } + + identity SYSTEM_CAPABILITIES { + base LLDP_TLV; + description + "The primary functions of the device transmitting the + LLDP PDU and their administrative status"; + reference "IEEE Std 802.1AB"; + } + + identity MANAGEMENT_ADDRESS { + base LLDP_TLV; + description + "The address associated with the device transmitting the + LLDP PDU which can be used for higher-layer network + management"; + reference "IEEE Std 802.1AB"; + } + + // typedef statements + + typedef chassis-id-type { + type enumeration { + enum CHASSIS_COMPONENT { + description + "Chassis identifier based on the value of entPhysicalAlias + object defined in IETF RFC 2737"; + } + enum INTERFACE_ALIAS { + description + "Chassis identifier based on the value of ifAlias object + defined in IETF RFC 2863"; + } + enum PORT_COMPONENT { + description + "Chassis identifier based on the value of entPhysicalAlias + object defined in IETF RFC 2737 for a port or backplane + component"; + } + enum MAC_ADDRESS { + description + "Chassis identifier based on the value of a unicast source + address (encoded in network byte order and IEEE 802.3 + canonical bit order), of a port on the containing chassis + as defined in IEEE Std 802-2001"; + } + enum NETWORK_ADDRESS { + description + "Chassis identifier based on a network address, + associated with a particular chassis. The encoded address + is composed of two fields. The first field is a single + octet, representing the IANA AddressFamilyNumbers value + for the specific address type, and the second field is the + network address value"; + } + enum INTERFACE_NAME { + description + "Chassis identifier based on the name of the interface, + e.g., the value of ifName object defined in IETF RFC 2863"; + } + enum LOCAL { + description + "Chassis identifier based on a locally defined value"; + } + } + description + "Type definition with enumerations describing the source of + the chassis identifier"; + reference + "IEEE 802.1AB LLDP MIB"; + } + + typedef port-id-type { + type enumeration { + enum INTERFACE_ALIAS { + description + "Chassis identifier based on the value of ifAlias object + defined in IETF RFC 2863"; + } + enum PORT_COMPONENT { + description + "Port identifier based on the value of entPhysicalAlias + object defined in IETF RFC 2737 for a port component"; + } + enum MAC_ADDRESS { + description + "Port identifier based on the value of a unicast source + address (encoded in network byte order and IEEE 802.3 + canonical bit order) associated with a port"; + } + enum NETWORK_ADDRESS { + description + "Port identifier based on a network address, + associated with a particular port"; + } + enum INTERFACE_NAME { + description + "Port identifier based on the name of the interface, + e.g., the value of ifName object defined in IETF RFC 2863"; + } + enum AGENT_CIRCUIT_ID { + description + "Port identifer based on the circuit id in the DHCP + relay agent information option as defined in IETF + RFC 3046"; + } + enum LOCAL { + description + "Port identifier based on a locally defined alphanumeric + string"; + } + } + description + "Type definition with enumerations describing the basis of + the port identifier"; + reference + "IEEE 802.1AB LLDP MIB"; + } + + +} \ No newline at end of file diff --git a/models/yang/common/openconfig-messages.yang b/models/yang/common/openconfig-messages.yang new file mode 100644 index 000000000..894704479 --- /dev/null +++ b/models/yang/common/openconfig-messages.yang @@ -0,0 +1,221 @@ +module openconfig-messages { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/messages"; + + prefix "oc-messages"; + + // import some basic types + import openconfig-extensions { prefix "oc-ext"; } + import openconfig-system-logging { prefix "oc-log"; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + related to Syslog messages that a device may generate. + + These messages are historically obtained through the Syslog + transport, however this module allows for obtaining them through + an alternative transport, such as a Subscribe operation over an + RPC. + + This module does not usurp traditional syslog servers, which may + still be configured through the + /yang/system/openconfig-system.yang model, rather it provies the + Operator with an alternative method of consuming messages."; + + oc-ext:openconfig-version "0.0.1"; + + revision "2018-08-13" { + description + "Initial draft."; + reference "0.0.1"; + } + + // identity statements + + identity DEBUG_SERVICE { + description + "Base identity for debug services. Identities within this base + identity are to be augmented in by vendors."; + } + + // grouping statements + + grouping messages-config { + description + "Configuration data for defining Syslog message severity."; + + leaf severity { + type oc-log:syslog-severity; + description + "Specifies that only messages of the given severity (or + greater severity) are sent over the RPC. + + This is analogous to differentiating which severity is to be + sent to legacy Syslog servers, as opposed to local buffer or + files."; + } + } + + grouping messages-state { + description + "Operational state data for Syslog messages."; + + container message { + oc-ext:telemetry-atomic; + config false; + description + "Syslog messages the client is Subscribing to. This is all + messages currently configured to be sent according to + syslog-severity."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + + // Decide if it is OK to include ALL in this leaf. + leaf msg { + type string; + description + "Message payload. If other leafs within this container not + supported, this leaf MAY include the entire message, + inclding pri, procid, app-name etc.."; + } + + leaf priority { + type uint8; + description + "The Priority value (PRIVAL) represents both the + Facility and Severity."; + reference + "IETF RFC 5424, Section 6.2.1"; + } + + leaf app-name { + type string; + description + "The APP-NAME field SHOULD identify the device or + application that originated the message."; + reference + "IETF RFC 5424, Section 6.2.5."; + } + + leaf procid { + type string; + description + "PROCID is a value that is included in the message, having + no interoperable meaning, except that a change in the value + indicates there has been a discontinuity in syslog + reporting."; + reference + "IETF RFC 5424, Section 6.2.6."; + } + + leaf msgid { + type string; + description + "The MSGID SHOULD identify the type of message. For + example, a firewall might use the MSGID 'TCPIN' for + incoming TCP traffic and the MSGID 'TCPOUT' for outgoing + TCP traffic."; + reference + "IETF RFC 5424, Section 6.2.7."; + } + } + } + + grouping debug-messages-config { + description + "Configuration data for enabling debug messages."; + + leaf service { + type identityref { + base DEBUG_SERVICE; + } + description + "Enumeration of all services which can have debugging enabled. + Vendors are to augment this base identity with their platform + or OS specific debug options."; + } + + leaf enabled { + type boolean; + default false; + description + "Enable and disable debugging."; + } + } + + grouping debug-messages-top { + description + "Configuration data for enabling Syslog debug messages."; + + container debug-entries { + description + "Enclosing container for list of debugs to enable."; + + list debug-service { + key "service"; + description + "List of debugging entries."; + + leaf service { + type leafref { + path "../config/service"; + } + description + "Reference to the debug-enable service key."; + } + + container config { + description + "Configuration data for debug service entries."; + + uses debug-messages-config; + } + + container state { + config false; + description + "Operational state data for enabled debugs."; + uses debug-messages-config; + } + } + } + } + + grouping messages-top { + description + "Top-level grouping for Syslog messages."; + + container messages { + description + "Top-level container for Syslog messages."; + + container config { + description + "Configuration data for Syslog messages."; + + uses messages-config; + } + + container state { + config false; + description + "Operational state data for a Syslog messages."; + + uses messages-config; + uses messages-state; + } + uses debug-messages-top; + } + } + uses messages-top; +} diff --git a/models/yang/common/openconfig-packet-match-types.yang b/models/yang/common/openconfig-packet-match-types.yang new file mode 100644 index 000000000..1b93d5205 --- /dev/null +++ b/models/yang/common/openconfig-packet-match-types.yang @@ -0,0 +1,309 @@ +module openconfig-packet-match-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/packet-match-types"; + + prefix "oc-pkt-match-types"; + + // import some basic types + import openconfig-inet-types { prefix oc-inet; } + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines common types for use in models requiring + data definitions related to packet matches."; + + oc-ext:openconfig-version "1.0.2"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "1.0.2"; + } + + revision "2018-04-15" { + description + "Corrected description and range for ethertype typedef"; + reference "1.0.1"; + } + + revision "2017-05-26" { + description + "Separated IP matches into AFs"; + reference "1.0.0"; + } + + revision "2016-08-08" { + description + "OpenConfig public release"; + reference "0.2.0"; + } + + revision "2016-04-27" { + description + "Initial revision"; + reference "TBD"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + + // extension statements + + // feature statements + + // identity statements + + + //TODO: should replace this with an official IEEE module + // when available. Only a select number of types are + // defined in this identity. + identity ETHERTYPE { + description + "Base identity for commonly used Ethertype values used + in packet header matches on Ethernet frames. The Ethertype + indicates which protocol is encapsulated in the Ethernet + payload."; + reference + "IEEE 802.3"; + } + + identity ETHERTYPE_IPV4 { + base ETHERTYPE; + description + "IPv4 protocol (0x0800)"; + } + + identity ETHERTYPE_ARP { + base ETHERTYPE; + description + "Address resolution protocol (0x0806)"; + } + + identity ETHERTYPE_VLAN { + base ETHERTYPE; + description + "VLAN-tagged frame (as defined by IEEE 802.1q) (0x8100). Note + that this value is also used to represent Shortest Path + Bridging (IEEE 801.1aq) frames."; + } + + identity ETHERTYPE_IPV6 { + base ETHERTYPE; + description + "IPv6 protocol (0x86DD)"; + } + + identity ETHERTYPE_MPLS { + base ETHERTYPE; + description + "MPLS unicast (0x8847)"; + } + + identity ETHERTYPE_LLDP { + base ETHERTYPE; + description + "Link Layer Discovery Protocol (0x88CC)"; + } + + identity ETHERTYPE_ROCE { + base ETHERTYPE; + description + "RDMA over Converged Ethernet (0x8915)"; + } + + + //TODO: should replace this with an official IANA module when + //available. Only a select set of protocols are defined with + //this identity. + identity IP_PROTOCOL { + description + "Base identity for commonly used IP protocols used in + packet header matches"; + reference + "IANA Assigned Internet Protocol Numbers"; + } + + identity IP_TCP { + base IP_PROTOCOL; + description + "Transmission Control Protocol (6)"; + } + + identity IP_UDP { + base IP_PROTOCOL; + description + "User Datagram Protocol (17)"; + } + + identity IP_ICMP { + base IP_PROTOCOL; + description + "Internet Control Message Protocol (1)"; + } + + identity IP_IGMP { + base IP_PROTOCOL; + description + "Internet Group Membership Protocol (2)"; + } + + identity IP_PIM { + base IP_PROTOCOL; + description + "Protocol Independent Multicast (103)"; + } + + identity IP_RSVP { + base IP_PROTOCOL; + description + "Resource Reservation Protocol (46)"; + } + + identity IP_GRE { + base IP_PROTOCOL; + description + "Generic Routing Encapsulation (47)"; + } + + identity IP_AUTH { + base IP_PROTOCOL; + description + "Authentication header, e.g., for IPSEC (51)"; + } + + identity IP_L2TP { + base IP_PROTOCOL; + description + "Layer Two Tunneling Protocol v.3 (115)"; + } + + + + identity TCP_FLAGS { + description + "Common TCP flags used in packet header matches"; + reference + "IETF RFC 793 - Transmission Control Protocol + IETF RFC 3168 - The Addition of Explicit Congestion + Notification (ECN) to IP"; + } + + identity TCP_SYN { + base TCP_FLAGS; + description + "TCP SYN flag"; + } + + identity TCP_FIN { + base TCP_FLAGS; + description + "TCP FIN flag"; + } + + identity TCP_RST { + base TCP_FLAGS; + description + "TCP RST flag"; + } + + identity TCP_PSH { + base TCP_FLAGS; + description + "TCP push flag"; + } + + identity TCP_ACK { + base TCP_FLAGS; + description + "TCP ACK flag"; + } + + identity TCP_URG { + base TCP_FLAGS; + description + "TCP urgent flag"; + } + + identity TCP_ECE { + base TCP_FLAGS; + description + "TCP ECN-Echo flag. If the SYN flag is set, indicates that + the TCP peer is ECN-capable, otherwise indicates that a + packet with Congestion Experienced flag in the IP header + is set"; + } + + identity TCP_CWR { + base TCP_FLAGS; + description + "TCP Congestion Window Reduced flag"; + } + + // typedef statements + + typedef port-num-range { + type union { + type string { + pattern '^(6[0-5][0-5][0-3][0-5]|[0-5]?[0-9]?[0-9]?[0-9]?' + + '[0-9]?)\.\.(6[0-5][0-5][0-3][0-5]|[0-5]?[0-9]?[0-9]?' + + '[0-9]?[0-9]?)$'; + } + type oc-inet:port-number; + type enumeration { + enum ANY { + description + "Indicates any valid port number (e.g., wildcard)"; + } + } + } + description + "Port numbers may be represented as a single value, + an inclusive range as .., or as ANY to + indicate a wildcard."; + } + + typedef ip-protocol-type { + type union { + type uint8 { + range 0..254; + } + type identityref { + base IP_PROTOCOL; + } + } + description + "The IP protocol number may be expressed as a valid protocol + number (integer) or using a protocol type defined by the + IP_PROTOCOL identity"; + } + + typedef ethertype-type { + type union { + type uint16 { + range 1536..65535; + } + type identityref { + base ETHERTYPE; + } + } + description + "The Ethertype value may be expressed as a 16-bit number in + decimal notation, or using a type defined by the + ETHERTYPE identity"; + } + +} diff --git a/models/yang/common/openconfig-packet-match.yang b/models/yang/common/openconfig-packet-match.yang new file mode 100644 index 000000000..510bc5768 --- /dev/null +++ b/models/yang/common/openconfig-packet-match.yang @@ -0,0 +1,371 @@ +module openconfig-packet-match { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/header-fields"; + + prefix "oc-pkt-match"; + + // import some basic types + import openconfig-inet-types { prefix oc-inet; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-packet-match-types { prefix oc-pkt-match-types; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines data related to packet header fields + used in matching operations, for example in ACLs. When a + field is omitted from a match expression, the effect is a + wildcard ('any') for that field."; + + oc-ext:openconfig-version "1.1.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "1.1.1"; + } + + revision "2017-12-15" { + description + "Add MPLS packet field matches"; + reference "1.1.0"; + } + + revision "2017-05-26" { + description + "Separated IP matches into AFs"; + reference "1.0.0"; + } + + revision "2016-08-08" { + description + "OpenConfig public release"; + reference "0.2.0"; + } + + revision "2016-04-27" { + description + "Initial revision"; + reference "TBD"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + + // Physical Layer fields + // ethernet-header + grouping ethernet-header-config { + description + "Configuration data of fields in Ethernet header."; + + leaf source-mac { + type oc-yang:mac-address; + description + "Source IEEE 802 MAC address."; + } + + leaf source-mac-mask { + type oc-yang:mac-address; + description + "Source IEEE 802 MAC address mask."; + } + + leaf destination-mac { + type oc-yang:mac-address; + description + "Destination IEEE 802 MAC address."; + } + + leaf destination-mac-mask { + type oc-yang:mac-address; + description + "Destination IEEE 802 MAC address mask."; + } + + leaf ethertype { + type oc-pkt-match-types:ethertype-type; + description + "Ethertype field to match in Ethernet packets"; + } + } + + grouping ethernet-header-state { + description + "State information of fields in Ethernet header."; + } + + grouping ethernet-header-top { + description + "Top level container for fields in Ethernet header."; + + container l2 { + description + "Ethernet header fields"; + + container config { + description + "Configuration data"; + uses ethernet-header-config; + } + + container state { + config false; + description + "State Information."; + uses ethernet-header-config; + uses ethernet-header-state; + } + } + } + + grouping mpls-header-top { + description + "Top-level container for fields in an MPLS header."; + + container mpls { + description + "MPLS header fields"; + + container config { + description + "Configuration parameters relating to fields within + the MPLS header."; + uses mpls-header-config; + } + + container state { + config false; + description + "Operational state parameters relating to fields + within the MPLS header"; + uses mpls-header-config; + } + } + } + + grouping mpls-header-config { + description + "Configuration parameters relating to matches within + MPLS header fields."; + + leaf traffic-class { + type uint8 { + range "0..7"; + } + description + "The value of the MPLS traffic class (TC) bits, + formerly known as the EXP bits."; + } + } + + grouping ip-protocol-fields-common-config { + description + "IP protocol fields common to IPv4 and IPv6"; + + leaf dscp { + type oc-inet:dscp; + description + "Value of diffserv codepoint."; + } + + leaf protocol { + type oc-pkt-match-types:ip-protocol-type; + description + "The protocol carried in the IP packet, expressed either + as its IP protocol number, or by a defined identity."; + } + + leaf hop-limit { + type uint8 { + range 0..255; + } + description + "The IP packet's hop limit -- known as TTL (in hops) in + IPv4 packets, and hop limit in IPv6"; + } + } + + // IP Layer + // ip-protocol-fields + grouping ipv4-protocol-fields-config { + description + "Configuration data of IP protocol fields + for IPv4"; + + leaf source-address { + type oc-inet:ipv4-prefix; + description + "Source IPv4 address prefix."; + } + + leaf destination-address { + type oc-inet:ipv4-prefix; + description + "Destination IPv4 address prefix."; + } + + uses ip-protocol-fields-common-config; + + } + + grouping ipv4-protocol-fields-state { + description + "State information of IP header fields for IPv4"; + } + + grouping ipv4-protocol-fields-top { + description + "IP header fields for IPv4"; + + container ipv4 { + description + "Top level container for IPv4 match field data"; + + container config { + description + "Configuration data for IPv4 match fields"; + uses ipv4-protocol-fields-config; + } + + container state { + config false; + description + "State information for IPv4 match fields"; + uses ipv4-protocol-fields-config; + uses ipv4-protocol-fields-state; + } + } + } + + grouping ipv6-protocol-fields-config { + description + "Configuration data for IPv6 match fields"; + + leaf source-address { + type oc-inet:ipv6-prefix; + description + "Source IPv6 address prefix."; + } + + leaf source-flow-label { + type oc-inet:ipv6-flow-label; + description + "Source IPv6 Flow label."; + } + + leaf destination-address { + type oc-inet:ipv6-prefix; + description + "Destination IPv6 address prefix."; + } + + leaf destination-flow-label { + type oc-inet:ipv6-flow-label; + description + "Destination IPv6 Flow label."; + } + + uses ip-protocol-fields-common-config; + } + + grouping ipv6-protocol-fields-state { + description + "Operational state data for IPv6 match fields"; + } + + grouping ipv6-protocol-fields-top { + description + "Top-level grouping for IPv6 match fields"; + + container ipv6 { + description + "Top-level container for IPv6 match field data"; + + container config { + description + "Configuration data for IPv6 match fields"; + + uses ipv6-protocol-fields-config; + } + + container state { + + config false; + + description + "Operational state data for IPv6 match fields"; + + uses ipv6-protocol-fields-config; + uses ipv6-protocol-fields-state; + } + } + } + + // Transport fields + grouping transport-fields-config { + description + "Configuration data of transport-layer packet fields"; + + leaf source-port { + type oc-pkt-match-types:port-num-range; + description + "Source port or range"; + } + + leaf destination-port { + type oc-pkt-match-types:port-num-range; + description + "Destination port or range"; + } + + leaf-list tcp-flags { + type identityref { + base oc-pkt-match-types:TCP_FLAGS; + } + description + "List of TCP flags to match"; + } + } + + grouping transport-fields-state { + description + "State data of transport-fields"; + } + + grouping transport-fields-top { + description + "Destination transport-fields top level grouping"; + + container transport { + description + "Transport fields container"; + + container config { + description + "Configuration data"; + uses transport-fields-config; + } + + container state { + config false; + description + "State data"; + uses transport-fields-config; + uses transport-fields-state; + } + } + } + +} diff --git a/models/yang/common/openconfig-platform-types.yang b/models/yang/common/openconfig-platform-types.yang new file mode 100644 index 000000000..8dc3ffc1f --- /dev/null +++ b/models/yang/common/openconfig-platform-types.yang @@ -0,0 +1,347 @@ +module openconfig-platform-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/platform-types"; + + prefix "oc-platform-types"; + + import openconfig-types { prefix oc-types; } + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines data types (e.g., YANG identities) + to support the OpenConfig component inventory model."; + + oc-ext:openconfig-version "1.0.0"; + + revision "2019-06-03" { + description + "Add OpenConfig component operating system patch type."; + reference "1.0.0"; + } + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.10.1"; + } + + revision "2018-11-16" { + description + "Added FEC_MODE_TYPE and FEC_STATUS_TYPE"; + reference "0.10.0"; + } + + revision "2018-05-05" { + description + "Added min-max-time to + avg-min-max-instant-stats-precision1-celsius, + added new CONTROLLER_CARD identity"; + reference "0.9.0"; + } + + revision "2018-01-16" { + description + "Added new per-component common data; add temp alarm"; + reference "0.8.0"; + } + + revision "2017-12-14" { + description + "Added anchor containers for component data, added new + component types"; + reference "0.7.0"; + } + + revision "2017-08-16" { + description + "Added power state enumerated type"; + reference "0.6.0"; + } + + revision "2016-12-22" { + description + "Added temperature state variable to component"; + reference "0.5.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // grouping statements + + + grouping avg-min-max-instant-stats-precision1-celsius { + description + "Common grouping for recording temperature values in + Celsius with 1 decimal precision. Values include the + instantaneous, average, minimum, and maximum statistics"; + + leaf instant { + type decimal64 { + fraction-digits 1; + } + units celsius; + description + "The instantaneous value of the statistic."; + } + + leaf avg { + type decimal64 { + fraction-digits 1; + } + units celsius; + description + "The arithmetic mean value of the statistic over the + sampling period."; + } + + leaf min { + type decimal64 { + fraction-digits 1; + } + units celsius; + description + "The minimum value of the statistic over the sampling + period"; + } + + leaf max { + type decimal64 { + fraction-digits 1; + } + units celsius; + description + "The maximum value of the statistic over the sampling + period"; + } + + uses oc-types:stat-interval-state; + uses oc-types:min-max-time; + } + + // identity statements + + identity OPENCONFIG_HARDWARE_COMPONENT { + description + "Base identity for hardware related components in a managed + device. Derived identities are partially based on contents + of the IANA Entity MIB."; + reference + "IANA Entity MIB and RFC 6933"; + } + + + identity OPENCONFIG_SOFTWARE_COMPONENT { + description + "Base identity for software-related components in a managed + device"; + } + + // hardware types + + identity CHASSIS { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Chassis component, typically with multiple slots / shelves"; + } + + identity BACKPLANE { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Backplane component for aggregating traffic, typically + contained in a chassis component"; + } + + identity FABRIC { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Interconnect between ingress and egress ports on the + device (e.g., a crossbar switch)."; + } + + identity POWER_SUPPLY { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Component that is supplying power to the device"; + } + + identity FAN { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Cooling fan, or could be some other heat-reduction component"; + } + + identity SENSOR { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Physical sensor, e.g., a temperature sensor in a chassis"; + } + + identity FRU { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Replaceable hardware component that does not have a more + specific defined schema."; + } + + identity LINECARD { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Linecard component, typically inserted into a chassis slot"; + } + + identity CONTROLLER_CARD { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "A type of linecard whose primary role is management or control + rather than data forwarding."; + } + + identity PORT { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Physical port, e.g., for attaching pluggables and networking + cables"; + } + + identity TRANSCEIVER { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Pluggable module present in a port"; + } + + identity CPU { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "Processing unit, e.g., a management processor"; + } + + identity STORAGE { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "A storage subsystem on the device (disk, SSD, etc.)"; + } + + identity INTEGRATED_CIRCUIT { + base OPENCONFIG_HARDWARE_COMPONENT; + description + "A special purpose processing unit, typically for traffic + switching/forwarding (e.g., switching ASIC, NPU, forwarding + chip, etc.)"; + } + + identity OPERATING_SYSTEM { + base OPENCONFIG_SOFTWARE_COMPONENT; + description + "Operating system running on a component"; + } + + identity OPERATING_SYSTEM_UPDATE { + base OPENCONFIG_SOFTWARE_COMPONENT; + description + "An operating system update - which should be a subcomponent + of the `OPERATING_SYSTEM` running on a component. An update is + defined to be a set of software changes that are atomically + installed (and uninstalled) together. Multiple updates may be + present for the Operating System. A system should not list all + installed software packages using this type -- but rather + updates that are bundled together as a single installable + item"; + } + + identity COMPONENT_OPER_STATUS { + description + "Current operational status of a platform component"; + } + + identity ACTIVE { + base COMPONENT_OPER_STATUS; + description + "Component is enabled and active (i.e., up)"; + } + + identity INACTIVE { + base COMPONENT_OPER_STATUS; + description + "Component is enabled but inactive (i.e., down)"; + } + + identity DISABLED { + base COMPONENT_OPER_STATUS; + description + "Component is administratively disabled."; + } + + identity FEC_MODE_TYPE { + description + "Base identity for FEC operational modes."; + } + + identity FEC_ENABLED { + base FEC_MODE_TYPE; + description + "FEC is administratively enabled."; + } + + identity FEC_DISABLED { + base FEC_MODE_TYPE; + description + "FEC is administratively disabled."; + } + + identity FEC_AUTO { + base FEC_MODE_TYPE; + description + "System will determine whether to enable or disable + FEC on a transceiver."; + } + + identity FEC_STATUS_TYPE { + description + "Base identity for FEC operational statuses."; + } + + identity FEC_STATUS_LOCKED { + base FEC_STATUS_TYPE; + description + "FEC is operationally locked."; + } + + identity FEC_STATUS_UNLOCKED { + base FEC_STATUS_TYPE; + description + "FEC is operationally unlocked."; + } + + // typedef statements + + typedef component-power-type { + type enumeration { + enum POWER_ENABLED { + description + "Enable power on the component"; + } + enum POWER_DISABLED { + description + "Disable power on the component"; + } + } + description + "A generic type reflecting whether a hardware component + is powered on or off"; + } + +} diff --git a/models/yang/common/openconfig-procmon.yang b/models/yang/common/openconfig-procmon.yang new file mode 100644 index 000000000..3c1013f47 --- /dev/null +++ b/models/yang/common/openconfig-procmon.yang @@ -0,0 +1,175 @@ +module openconfig-procmon { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/system/procmon"; + + prefix "oc-proc"; + + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + import openconfig-types { prefix oc-types; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module provides data definitions for process health + monitoring of one or more processes running on the system."; + + oc-ext:openconfig-version "0.3.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.3.1"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // grouping statements + + grouping procmon-processes-top { + description + "Top level grouping for attributes for processes."; + + container processes { + description + "Parameters related to all monitored processes"; + + list process { + key "pid"; + config false; + description + "List of monitored processes"; + + leaf pid { + type leafref { + path "../state/pid"; + } + description + "Reference to the process pid key"; + } + + container state { + config false; + description + "State parameters related to monitored processes"; + + uses procmon-process-attributes-state; + } + } + } + } + + grouping procmon-process-attributes-state { + description + "Attributes state definitions for a process"; + + leaf pid { + type uint64; + description + "The process pid"; + } + + leaf name { + type string; + description + "The process name"; + } + + leaf-list args { + type string; + description + "Current process command line arguments. Arguments with + a parameter (e.g., --option 10 or -option=10) should be + represented as a single element of the list with the + argument name and parameter together. Flag arguments, i.e., + those without a parameter should also be in their own list + element."; + } + + leaf start-time { + type uint64; + units "ns"; + description + "The time at which this process started, + reported as nanoseconds since the UNIX epoch. The + system must be synchronized such that the start-time + can be reported accurately, otherwise it should not be + reported."; + } + + leaf uptime { + type oc-types:timeticks64; + description + "Amount of time elapsed since this process started."; + } + + leaf cpu-usage-user { + type oc-types:timeticks64; + description + "CPU time consumed by this process in user mode."; + } + + leaf cpu-usage-system { + type oc-types:timeticks64; + description + "CPU time consumed by this process in kernel mode."; + } + + leaf cpu-utilization { + type oc-types:percentage; + description + "The percentage of CPU that is being used by the process."; + } + + leaf memory-usage { + type uint64; + units "bytes"; + description + "Bytes allocated and still in use by the process"; + } + + leaf memory-utilization { + type oc-types:percentage; + description + "The percentage of RAM that is being used by the process."; + } + } + + // augment statements + + // rpc statements + + // notification statements +} diff --git a/models/yang/common/openconfig-system-logging.yang b/models/yang/common/openconfig-system-logging.yang new file mode 100644 index 000000000..1602cb1c6 --- /dev/null +++ b/models/yang/common/openconfig-system-logging.yang @@ -0,0 +1,503 @@ +module openconfig-system-logging { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/system/logging"; + + prefix "oc-log"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + import openconfig-inet-types { prefix oc-inet; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + for common logging facilities on network systems."; + + oc-ext:openconfig-version "0.3.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.3.1"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // extension statements + + // feature statements + + // identity statements + + identity SYSLOG_FACILITY { + description + "Base identity for Syslog message facilities."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity ALL { + base SYSLOG_FACILITY; + description + "All supported facilities"; + } + + identity KERNEL { + base SYSLOG_FACILITY; + description + "The facility for kernel messages"; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity USER { + base SYSLOG_FACILITY; + description + "The facility for user-level messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity MAIL { + base SYSLOG_FACILITY; + description + "The facility for the mail system."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity SYSTEM_DAEMON { + base SYSLOG_FACILITY; + description + "The facility for the system daemons."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity AUTH { + base SYSLOG_FACILITY; + description + "The facility for security/authorization messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity SYSLOG { + base SYSLOG_FACILITY; + description + "The facility for messages generated internally by syslogd + facility."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity AUTHPRIV { + base SYSLOG_FACILITY; + description + "The facility for privileged security/authorization messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + + identity NTP { + base SYSLOG_FACILITY; + description + "The facility for the NTP subsystem."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity AUDIT { + base SYSLOG_FACILITY; + description + "The facility for log audit messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity CONSOLE { + base SYSLOG_FACILITY; + description + "The facility for log alert messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL0 { + base SYSLOG_FACILITY; + description + "The facility for local use 0 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL1 { + base SYSLOG_FACILITY; + description + "The facility for local use 1 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL2 { + base SYSLOG_FACILITY; + description + "The facility for local use 2 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL3 { + base SYSLOG_FACILITY; + description + "The facility for local use 3 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL4 { + base SYSLOG_FACILITY; + description + "The facility for local use 4 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL5 { + base SYSLOG_FACILITY; + description + "The facility for local use 5 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL6 { + base SYSLOG_FACILITY; + description + "The facility for local use 6 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOCAL7 { + base SYSLOG_FACILITY; + description + "The facility for local use 7 messages."; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + identity LOG_DESTINATION_TYPE { + description + "Base identity for destination for logging messages"; + } + + identity DEST_CONSOLE { + base LOG_DESTINATION_TYPE; + description + "Directs log messages to the console"; + } + + identity DEST_BUFFER { + base LOG_DESTINATION_TYPE; + description + "Directs log messages to and in-memory circular buffer"; + } + + identity DEST_FILE { + base LOG_DESTINATION_TYPE; + description + "Directs log messages to a local file"; + } + + identity DEST_REMOTE { + base LOG_DESTINATION_TYPE; + description + "Directs log messages to a remote syslog server"; + } + + // typedef statements + + typedef syslog-severity { + type enumeration { + enum EMERGENCY { + description + "Emergency: system is unusable (0)"; + } + enum ALERT { + description + "Alert: action must be taken immediately (1)"; + } + enum CRITICAL { + description + "Critical: critical conditions (2)"; + } + enum ERROR { + description + "Error: error conditions (3)"; + } + enum WARNING { + description + "Warning: warning conditions (4)"; + } + enum NOTICE { + description + "Notice: normal but significant condition(5)"; + } + enum INFORMATIONAL { + description + "Informational: informational messages (6)"; + } + enum DEBUG { + description + "Debug: debug-level messages (7)"; + } + } + description + "Syslog message severities"; + reference + "IETF RFC 5424 - The Syslog Protocol"; + } + + // grouping statements + + grouping logging-selectors-config { + description + "Configuration data for logging selectors"; + + leaf facility { + type identityref { + base SYSLOG_FACILITY; + } + description + "Specifies the facility, or class of messages to log"; + } + + leaf severity { + type syslog-severity; + description + "Specifies that only messages of the given severity (or + greater severity) for the corresonding facility are logged"; + } + } + + grouping logging-selectors-state { + description + "Operational state data for logging selectors"; + } + + grouping logging-selectors-top { + description + "Top-level grouping for the logging selector list"; + + container selectors { + description + "Enclosing container "; + + list selector { + key "facility severity"; + description + "List of selectors for log messages"; + + leaf facility { + type leafref { + path "../config/facility"; + } + description + "Reference to facility list key"; + } + + leaf severity { + type leafref { + path "../config/severity"; + } + description + "Reference to severity list key"; + } + + container config { + description + "Configuration data "; + + uses logging-selectors-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses logging-selectors-config; + uses logging-selectors-state; + } + } + } + } + + grouping logging-console-config { + description + "Configuration data for console logging"; + } + + grouping logging-console-state { + description + "Operational state data for console logging"; + } + + grouping logging-console-top { + description + "Top-level grouping for console logging data"; + + container console { + description + "Top-level container for data related to console-based + logging"; + + container config { + description + "Configuration data for console logging"; + + uses logging-console-config; + } + + container state { + + config false; + + description + "Operational state data for console logging"; + + uses logging-console-config; + uses logging-console-state; + } + + uses logging-selectors-top; + } + } + + grouping logging-remote-config { + description + "Configuration data for remote log servers"; + + leaf host { + type oc-inet:host; + description + "IP address or hostname of the remote log server"; + } + + leaf source-address { + type oc-inet:ip-address; + description + "Source IP address for packets to the log server"; + } + + leaf remote-port { + type oc-inet:port-number; + default 514; + description + "Sets the destination port number for syslog UDP messages to + the server. The default for syslog is 514."; + } + } + + grouping logging-remote-state { + description + "Operational state data for remote log servers"; + } + + grouping logging-remote-top { + description + "Top-level grouping for remote log servers"; + + container remote-servers { + description + "Enclosing container for the list of remote log servers"; + + list remote-server { + key "host"; + description + "List of remote log servers"; + + leaf host { + type leafref { + path "../config/host"; + } + description + "Reference to the host list key"; + } + + container config { + description + "Configuration data for remote log servers"; + + uses logging-remote-config; + } + + container state { + + config false; + + description + "Operational state data for remote log servers"; + + uses logging-remote-config; + uses logging-remote-state; + } + uses logging-selectors-top; + } + } + } + + grouping logging-top { + description + "Top-level grouping for logging data"; + + container logging { + description + "Top-level container for data related to logging / syslog"; + + uses logging-console-top; + uses logging-remote-top; + } + } + // data definition statements + + // augment statements + + +} \ No newline at end of file diff --git a/models/yang/common/openconfig-system-management.yang b/models/yang/common/openconfig-system-management.yang new file mode 100644 index 000000000..00494b7a7 --- /dev/null +++ b/models/yang/common/openconfig-system-management.yang @@ -0,0 +1,138 @@ +module openconfig-system-management { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/system/management"; + + prefix "oc-sys-mgmt"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + import openconfig-inet-types { prefix oc-inet; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + related to management services."; + + oc-ext:openconfig-version "0.1.2"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.1.2"; + } + + revision "2018-08-28" { + description + "Update description of the ANY enum."; + reference "0.1.1"; + } + + revision "2018-07-26" { + description + "Initial public release"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + + // typedef statements + + // grouping statements + + grouping system-grpc-server-config { + description + "Configuration data for the gRPC server"; + + leaf enable { + type boolean; + default true; + description + "Enables the gRPC server. The gRPC server is enabled by + default"; + } + + leaf port { + type oc-inet:port-number; + default 9339; + description + "TCP port on which the gRPC server should listen"; + } + + leaf transport-security { + type boolean; + description + "Enables gRPC transport security (e.g., TLS or SSL)"; + } + + leaf certificate-id { + type string; + description + "The certificate ID to be used for authentication"; + } + + leaf-list listen-addresses { + type union { + type oc-inet:ip-address; + type enumeration { + enum ANY { + description + "The gRPC daemon should listen on any address + bound to an interface on the system."; + } + } + } + description + "The IP addresses that the gRPC server should listen + on. This may be an IPv4 or an IPv6 address"; + } + } + + grouping system-grpc-server-top { + description + "Top-level grouping for system gRPC server data"; + + container grpc-server { + description + "Top-level container for the gRPC server"; + + container config { + description + "Configuration data for the system gRPC server"; + + uses system-grpc-server-config; + } + + container state { + config false; + + description + "Operational state data for the system gRPC server"; + + uses system-grpc-server-config; + } + } + } + + // data definition statements + + // augment statements + + // rpc statements + + // notification statements + +} diff --git a/models/yang/common/openconfig-system-terminal.yang b/models/yang/common/openconfig-system-terminal.yang new file mode 100644 index 000000000..b34811c99 --- /dev/null +++ b/models/yang/common/openconfig-system-terminal.yang @@ -0,0 +1,249 @@ +module openconfig-system-terminal { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/system/terminal"; + + prefix "oc-sys-term"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + related to remote terminal services such as ssh and telnet."; + + oc-ext:openconfig-version "0.3.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.3.1"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + + // typedef statements + + // grouping statements + + grouping system-terminal-common-config { + description + "Common configuration data for terminal services"; + + leaf timeout { + type uint16; + units seconds; + description + "Set the idle timeout in seconds on terminal connections to + the system for the protocol."; + } + + leaf rate-limit { + type uint16; + units "conn/min"; + description + "Set a limit on the number of connection attempts per + minute to the system for the protocol."; + } + + leaf session-limit { + type uint16; + description + "Set a limit on the number of simultaneous active terminal + sessions to the system for the protocol (e.g., ssh, + telnet, ...) "; + } + } + + grouping system-terminal-common-state { + description + "Common operational state data for terminal services"; + } + + grouping system-terminal-common-top { + description + "Top-level grouping for common terminal service data"; + + container terminal-servers { + description + "Top-level container for terminal services"; + + container config { + description + "Configuration data for terminal services"; + + uses system-terminal-common-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses system-terminal-common-config; + uses system-terminal-common-state; + } + } + } + + grouping system-ssh-server-config { + description + "Configuration data for system ssh configuration"; + + leaf enable { + type boolean; + default true; + description + "Enables the ssh server. The ssh server is enabled by + default."; + } + + leaf protocol-version { + type enumeration { + enum V2 { + description + "Use SSH v2 only"; + } + enum V1 { + description + "Use SSH v1 only"; + } + enum V1_V2 { + description + "Use either SSH v1 or v2"; + } + } + default V2; + description + "Set the protocol version for SSH connections to the system"; + } + + uses system-terminal-common-config; + } + + grouping system-ssh-server-state { + description + "Operational state data for ssh server"; + } + + grouping system-ssh-server-top { + description + "Top-level grouping for ssh server data"; + + container ssh-server { + description + "Top-level container for ssh server"; + + container config { + description + "Configuration data for the system ssh server"; + + uses system-ssh-server-config; + } + + container state { + + config false; + + description + "Operational state data for the system ssh server"; + + uses system-ssh-server-config; + uses system-ssh-server-state; + } + } + } + + grouping system-telnet-server-config { + description + "Configuration data for telnet server"; + + leaf enable { + type boolean; + default false; + description + "Enables the telnet server. Telnet is disabled by + default"; + } + uses system-terminal-common-config; + + } + + grouping system-telnet-server-state { + description + "Operational state data for telnet server"; + } + + grouping system-telnet-server-top { + description + "Top-level grouping for telnet server "; + + container telnet-server { + description + "Top-level container for telnet terminal servers"; + + container config { + description + "Configuration data for telnet"; + + uses system-telnet-server-config; + } + + container state { + + config false; + + description + "Operational state data for telnet"; + + uses system-telnet-server-config; + uses system-telnet-server-state; + } + } + } + + // data definition statements + + // augment statements + + // rpc statements + + // notification statements + +} \ No newline at end of file diff --git a/models/yang/common/openconfig-types.yang b/models/yang/common/openconfig-types.yang new file mode 100644 index 000000000..a7ba45d91 --- /dev/null +++ b/models/yang/common/openconfig-types.yang @@ -0,0 +1,466 @@ +module openconfig-types { + yang-version "1"; + + namespace "http://openconfig.net/yang/openconfig-types"; + + prefix "oc-types"; + + // import statements + import openconfig-extensions { prefix oc-ext; } + + // meta + organization + "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "This module contains a set of general type definitions that + are used across OpenConfig models. It can be imported by modules + that make use of these types."; + + oc-ext:openconfig-version "0.5.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.5.1"; + } + + revision "2018-05-05" { + description + "Add grouping of min-max-time and + included them to all stats with min/max/avg"; + reference "0.5.0"; + } + + revision "2018-01-16" { + description + "Add interval to min/max/avg stats; add percentage stat"; + reference "0.4.0"; + } + + revision "2017-08-16" { + description + "Apply fix for ieetfloat32 length parameter"; + reference "0.3.3"; + } + + revision "2017-01-13" { + description + "Add ADDRESS_FAMILY identity"; + reference "0.3.2"; + } + + revision "2016-11-14" { + description + "Correct length of ieeefloat32"; + reference "0.3.1"; + } + + revision "2016-11-11" { + description + "Additional types - ieeefloat32 and routing-password"; + reference "0.3.0"; + } + + revision "2016-05-31" { + description + "OpenConfig public release"; + reference "0.2.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + typedef percentage { + type uint8 { + range "0..100"; + } + description + "Integer indicating a percentage value"; + } + + typedef std-regexp { + type string; + description + "This type definition is a placeholder for a standard + definition of a regular expression that can be utilised in + OpenConfig models. Further discussion is required to + consider the type of regular expressions that are to be + supported. An initial proposal is POSIX compatible."; + } + + typedef timeticks64 { + type uint64; + description + "This type is based on the timeticks type defined in + RFC 6991, but with 64-bit width. It represents the time, + modulo 2^64, in hundredths of a second between two epochs."; + reference + "RFC 6991 - Common YANG Data Types"; + } + + typedef ieeefloat32 { + type binary { + length "4"; + } + description + "An IEEE 32-bit floating point number. The format of this number + is of the form: + 1-bit sign + 8-bit exponent + 23-bit fraction + The floating point value is calculated using: + (-1)**S * 2**(Exponent-127) * (1+Fraction)"; + } + + typedef routing-password { + type string; + description + "This type is indicative of a password that is used within + a routing protocol which can be returned in plain text to the + NMS by the local system. Such passwords are typically stored + as encrypted strings. Since the encryption used is generally + well known, it is possible to extract the original value from + the string - and hence this format is not considered secure. + Leaves specified with this type should not be modified by + the system, and should be returned to the end-user in plain + text. This type exists to differentiate passwords, which + may be sensitive, from other string leaves. It could, for + example, be used by the NMS to censor this data when + viewed by particular users."; + } + + typedef stat-interval { + type uint64; + units nanoseconds; + description + "A time interval over which a set of statistics is computed. + A common usage is to report the interval over which + avg/min/max stats are computed and reported."; + } + + grouping stat-interval-state { + description + "Reusable leaf definition for stats computation interval"; + + leaf interval { + type oc-types:stat-interval; + description + "If supported by the system, this reports the time interval + over which the min/max/average statistics are computed by + the system."; + } + } + + grouping min-max-time { + description + "Common grouping for recording the absolute time at which + the minimum and maximum values occurred in the statistics"; + + leaf min-time { + type oc-types:timeticks64; + description + "The absolute time at which the minimum value occurred. + The value is the timestamp in nanoseconds relative to + the Unix Epoch (Jan 1, 1970 00:00:00 UTC)."; + } + + leaf max-time { + type oc-types:timeticks64; + description + "The absolute time at which the maximum value occurred. + The value is the timestamp in nanoseconds relative to + the Unix Epoch (Jan 1, 1970 00:00:00 UTC)."; + } + } + + grouping avg-min-max-stats-precision1 { + description + "Common nodes for recording average, minimum, and + maximum values for a statistic. These values all have + fraction-digits set to 1. Statistics are computed + and reported based on a moving time interval (e.g., the last + 30s). If supported by the device, the time interval over which + the statistics are computed is also reported."; + + leaf avg { + type decimal64 { + fraction-digits 1; + } + description + "The arithmetic mean value of the statistic over the + time interval."; + } + + leaf min { + type decimal64 { + fraction-digits 1; + } + description + "The minimum value of the statistic over the time + interval."; + } + + leaf max { + type decimal64 { + fraction-digits 1; + } + description + "The maximum value of the statitic over the time + interval."; + } + + uses stat-interval-state; + uses min-max-time; + } + + grouping avg-min-max-instant-stats-precision1 { + description + "Common grouping for recording an instantaneous statistic value + in addition to avg-min-max stats"; + + leaf instant { + type decimal64 { + fraction-digits 1; + } + description + "The instantaneous value of the statistic."; + } + + uses avg-min-max-stats-precision1; + } + + grouping avg-min-max-instant-stats-precision2-dB { + description + "Common grouping for recording dB values with 2 decimal + precision. Values include the instantaneous, average, + minimum, and maximum statistics. Statistics are computed + and reported based on a moving time interval (e.g., the last + 30s). If supported by the device, the time interval over which + the statistics are computed, and the times at which the minimum + and maximum values occurred, are also reported."; + + leaf instant { + type decimal64 { + fraction-digits 2; + } + units dB; + description + "The instantaneous value of the statistic."; + } + + leaf avg { + type decimal64 { + fraction-digits 2; + } + units dB; + description + "The arithmetic mean value of the statistic over the + time interval."; + } + + leaf min { + type decimal64 { + fraction-digits 2; + } + units dB; + description + "The minimum value of the statistic over the time interval."; + } + + leaf max { + type decimal64 { + fraction-digits 2; + } + units dB; + description + "The maximum value of the statistic over the time + interval."; + } + + uses stat-interval-state; + uses min-max-time; + } + + grouping avg-min-max-instant-stats-precision2-dBm { + description + "Common grouping for recording dBm values with 2 decimal + precision. Values include the instantaneous, average, + minimum, and maximum statistics. Statistics are computed + and reported based on a moving time interval (e.g., the last + 30s). If supported by the device, the time interval over which + the statistics are computed, and the times at which the minimum + and maximum values occurred, are also reported."; + + leaf instant { + type decimal64 { + fraction-digits 2; + } + units dBm; + description + "The instantaneous value of the statistic."; + } + + leaf avg { + type decimal64 { + fraction-digits 2; + } + units dBm; + description + "The arithmetic mean value of the statistic over the + time interval."; + } + + leaf min { + type decimal64 { + fraction-digits 2; + } + units dBm; + description + "The minimum value of the statistic over the time + interval."; + } + + leaf max { + type decimal64 { + fraction-digits 2; + } + units dBm; + description + "The maximum value of the statistic over the time interval."; + } + + uses stat-interval-state; + uses min-max-time; + } + + grouping avg-min-max-instant-stats-precision2-mA { + description + "Common grouping for recording mA values with 2 decimal + precision. Values include the instantaneous, average, + minimum, and maximum statistics. Statistics are computed + and reported based on a moving time interval (e.g., the last + 30s). If supported by the device, the time interval over which + the statistics are computed, and the times at which the minimum + and maximum values occurred, are also reported."; + + leaf instant { + type decimal64 { + fraction-digits 2; + } + units mA; + description + "The instantaneous value of the statistic."; + } + + leaf avg { + type decimal64 { + fraction-digits 2; + } + units mA; + description + "The arithmetic mean value of the statistic over the + time interval."; + } + + leaf min { + type decimal64 { + fraction-digits 2; + } + units mA; + description + "The minimum value of the statistic over the time + interval."; + } + + leaf max { + type decimal64 { + fraction-digits 2; + } + units mA; + description + "The maximum value of the statistic over the time + interval."; + } + + uses stat-interval-state; + uses min-max-time; + } + + grouping avg-min-max-instant-stats-pct { + description + "Common grouping for percentage statistics. + Values include the instantaneous, average, + minimum, and maximum statistics. Statistics are computed + and reported based on a moving time interval (e.g., the last + 30s). If supported by the device, the time interval over which + the statistics are computed, and the times at which the minimum + and maximum values occurred, are also reported."; + + leaf instant { + type oc-types:percentage; + description + "The instantaneous percentage value."; + } + + leaf avg { + type oc-types:percentage; + description + "The arithmetic mean value of the percentage measure of the + statistic over the time interval."; + } + + leaf min { + type oc-types:percentage; + description + "The minimum value of the percentage measure of the + statistic over the time interval."; + } + + leaf max { + type oc-types:percentage; + description + "The maximum value of the percentage measure of the + statistic over the time interval."; + } + + uses stat-interval-state; + uses min-max-time; + } + + identity ADDRESS_FAMILY { + description + "A base identity for all address families"; + } + + identity IPV4 { + base ADDRESS_FAMILY; + description + "The IPv4 address family"; + } + + identity IPV6 { + base ADDRESS_FAMILY; + description + "The IPv6 address family"; + } + + identity MPLS { + base ADDRESS_FAMILY; + description + "The MPLS address family"; + } + + identity L2_ETHERNET { + base ADDRESS_FAMILY; + description + "The 802.3 Ethernet address family"; + } + +} diff --git a/models/yang/common/openconfig-vlan-types.yang b/models/yang/common/openconfig-vlan-types.yang new file mode 100644 index 000000000..7778e19f0 --- /dev/null +++ b/models/yang/common/openconfig-vlan-types.yang @@ -0,0 +1,206 @@ +module openconfig-vlan-types { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/vlan-types"; + + prefix "oc-vlan-types"; + + // import some basic types + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "This module defines configuration and state variables for VLANs, + in addition to VLAN parameters associated with interfaces"; + + oc-ext:openconfig-version "3.0.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "3.0.1"; + } + + revision "2018-02-14" { + description + "Fix bug with name of 802.1ad identity."; + reference "3.0.0"; + } + + revision "2017-07-14" { + description + "Move top-level vlan data to network-instance; Update + identities to comply to style guide; fixed pattern + quoting; corrected trunk vlan types; added TPID config to + base interface."; + reference "2.0.0"; + } + + revision "2016-05-26" { + description + "OpenConfig public release"; + reference "1.0.2"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // extension statements + + // feature statements + + // identity statements + + identity TPID_TYPES { + description + "Base identity for TPID values that can override the VLAN + ethertype value"; + } + + identity TPID_0X8100 { + base TPID_TYPES; + description + "Default TPID value for 802.1q single-tagged VLANs."; + } + + identity TPID_0X88A8 { + base TPID_TYPES; + description + "TPID value for 802.1ad provider bridging, QinQ or + stacked VLANs."; + } + + identity TPID_0X9100 { + base TPID_TYPES; + description + "Alternate TPID value"; + } + + identity TPID_0X9200 { + base TPID_TYPES; + description + "Alternate TPID value"; + } + + // typedef statements + + // TODO: typedefs should be defined in a vlan-types.yang file. + typedef vlan-id { + type uint16 { + range 1..4094; + } + description + "Type definition representing a single-tagged VLAN"; + } + + typedef vlan-range { + type string { + // range specified as [lower]..[upper] + pattern '^(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])\.\.(409[0-4]|' + + '40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{1,2}|' + + '[1-9])$'; + } + description + "Type definition representing a range of single-tagged + VLANs. A range is specified as x..y where x and y are + valid VLAN IDs (1 <= vlan-id <= 4094). The range is + assumed to be inclusive, such that any VLAN-ID matching + x <= VLAN-ID <= y falls within the range."; + } + + typedef qinq-id { + type string { + pattern + '^(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])\.' + + '((409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])|\*)$'; + } + description + "Type definition representing a single double-tagged/QinQ VLAN + identifier. The format of a QinQ VLAN-ID is x.y where X is the + 'outer' VLAN identifier, and y is the 'inner' VLAN identifier. + Both x and y must be valid VLAN IDs (1 <= vlan-id <= 4094) + with the exception that y may be equal to a wildcard (*). In + cases where y is set to the wildcard, this represents all inner + VLAN identifiers where the outer VLAN identifier is equal to + x"; + } + + typedef qinq-id-range { + type union { + type string { + // match cases where the range is specified as x..y.z + pattern + '^(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])\.\.' + + '(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])\.' + + '((409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])|\*)$'; + } + type string { + // match cases where the range is specified as x.y..z + pattern + '^(\*|(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9]))\.' + + '(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])\.\.' + + '(409[0-4]|40[0-8][0-9]|[1-3][0-9]{3}|' + + '[1-9][0-9]{1,2}|[1-9])$'; + } + } + description + "A type definition representing a range of double-tagged/QinQ + VLAN identifiers. The format of a QinQ VLAN-ID range can be + specified in three formats. Where the range is outer VLAN IDs + the range is specified as x..y.z. In this case outer VLAN + identifiers meeting the criteria x <= outer-vlan-id <= y are + accepted iff the inner VLAN-ID is equal to y - or any inner-tag + if the wildcard is specified. Alternatively the range can be + specified as x.y..z. In this case only VLANs with an + outer-vlan-id qual to x are accepted (x may again be the + wildcard). Inner VLANs are accepted if they meet the inequality + y <= inner-vlan-id <= z."; + } + + typedef vlan-mode-type { + type enumeration { + enum ACCESS { + description "Access mode VLAN interface (No 802.1q header)"; + } + enum TRUNK { + description "Trunk mode VLAN interface"; + } + } + description + "VLAN interface mode (trunk or access)"; + } + + typedef vlan-ref { + type union { + type vlan-id; + type string; + // TODO: string should be changed to leafref to reference + // an existing VLAN. this is not allowed in YANG 1.0 but + // is expected to be in YANG 1.1. + // type leafref { + // path "vlan:vlans/vlan:vlan/vlan:config/vlan:name"; + // } + } + description + "Reference to a VLAN by name or id"; + } + +} diff --git a/models/yang/common/openconfig-vlan.yang b/models/yang/common/openconfig-vlan.yang new file mode 100644 index 000000000..1cae82476 --- /dev/null +++ b/models/yang/common/openconfig-vlan.yang @@ -0,0 +1,449 @@ +module openconfig-vlan { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/vlan"; + + prefix "oc-vlan"; + + // import some basic types + import openconfig-vlan-types { prefix oc-vlan-types; } + import openconfig-interfaces { prefix oc-if; } + import openconfig-if-ethernet { prefix oc-eth; } + import openconfig-if-aggregate { prefix oc-lag; } + import iana-if-type { prefix ift; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "This module defines configuration and state variables for VLANs, + in addition to VLAN parameters associated with interfaces"; + + oc-ext:openconfig-version "3.0.2"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "3.0.2"; + } + + revision "2018-06-05" { + description + "Fix bugs in when statements."; + reference "3.0.1"; + } + + revision "2018-02-14" { + description + "Fix bug with name of 802.1ad identity."; + reference "3.0.0"; + } + + revision "2017-07-14" { + description + "Move top-level vlan data to network-instance; Update + identities to comply to style guide; fixed pattern + quoting; corrected trunk vlan types; added TPID config to + base interface."; + reference "2.0.0"; + } + + revision "2016-05-26" { + description + "OpenConfig public release"; + reference "1.0.2"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // grouping statements + + grouping vlan-config { + description "VLAN configuration container."; + + leaf vlan-id { + type oc-vlan-types:vlan-id; + description "Interface VLAN id."; + } + + leaf name { + type string; + description "Interface VLAN name."; + } + + leaf status { + type enumeration { + enum ACTIVE { + description "VLAN is active"; + } + enum SUSPENDED { + description "VLAN is inactive / suspended"; + } + } + default ACTIVE; + description "Admin state of the VLAN"; + } + + } + + grouping vlan-state { + description "State variables for VLANs"; + + // placeholder + + } + + grouping vlan-tpid-config { + description + "TPID configuration for dot1q-enabled interfaces"; + + leaf tpid { + type identityref { + base oc-vlan-types:TPID_TYPES; + } + default oc-vlan-types:TPID_0X8100; + description + "Optionally set the tag protocol identifier field (TPID) that + is accepted on the VLAN"; + } + } + + grouping vlan-tpid-state { + description + "TPID opstate for dot1q-enabled interfaces"; + + // placeholder + + } + + grouping vlan-members-state { + description + "List of interfaces / subinterfaces belonging to the VLAN."; + + container members { + description + "Enclosing container for list of member interfaces"; + + list member { + config false; + description + "List of references to interfaces / subinterfaces + associated with the VLAN."; + + uses oc-if:base-interface-ref-state; + } + } + } + + grouping vlan-switched-config { + description + "VLAN related configuration that is part of the physical + Ethernet interface."; + + leaf interface-mode { + type oc-vlan-types:vlan-mode-type; + description + "Set the interface to access or trunk mode for + VLANs"; + } + + leaf native-vlan { + when "../interface-mode = 'TRUNK'" { + description + "Native VLAN is valid for trunk mode interfaces"; + } + type oc-vlan-types:vlan-id; + description + "Set the native VLAN id for untagged frames arriving on + a trunk interface. Tagged frames sent on an interface + configured with a native VLAN should have their tags + stripped prior to transmission. This configuration is only + valid on a trunk interface."; + } + + leaf access-vlan { + when "../interface-mode = 'ACCESS'" { + description + "Access VLAN assigned to the interfaces"; + } + type oc-vlan-types:vlan-id; + description + "Assign the access vlan to the access port."; + } + + leaf-list trunk-vlans { + when "../interface-mode = 'TRUNK'" { + description + "Allowed VLANs may be specified for trunk mode + interfaces."; + } + type union { + type oc-vlan-types:vlan-id; + type oc-vlan-types:vlan-range; + } + description + "Specify VLANs, or ranges thereof, that the interface may + carry when in trunk mode. If not specified, all VLANs are + allowed on the interface. Ranges are specified in the form + x..y, where x.'"; + } + } + + grouping acl-interfaces-state { + description + "Operational state data for interface references"; + } + + grouping acl-interfaces-top { + description + "Top-level grouping for interface-specific ACL data"; + + container interfaces { + description + "Enclosing container for the list of interfaces on which + ACLs are set"; + + list interface { + key "id"; + description + "List of interfaces on which ACLs are set"; + + leaf id { + type leafref { + path "../config/id"; + } + description + "Reference to the interface id list key"; + } + + container config { + description + "Configuration for ACL per-interface data"; + + uses acl-interfaces-config; + } + + container state { + + config false; + + description + "Operational state for ACL per-interface data"; + + uses acl-interfaces-config; + uses acl-interfaces-state; + } + + uses oc-if:interface-ref; + uses interface-ingress-acl-top; + uses interface-egress-acl-top; + } + } + } + + grouping acl-config { + description + "Global configuration data for ACLs"; + } + + grouping acl-state { + description + "Global operational state data for ACLs"; + + leaf counter-capability { + type identityref { + base ACL_COUNTER_CAPABILITY; + } + description + "System reported indication of how ACL counters are reported + by the target"; + } + } + grouping acl-top { + description + "Top level grouping for ACL data and structure"; + + container acl { + description + "Top level enclosing container for ACL model config + and operational state data"; + + container config { + description + "Global config data for ACLs"; + + uses acl-config; + } + + container state { + + config false; + + description + "Global operational state data for ACLs"; + + uses acl-config; + uses acl-state; + } + + uses acl-set-top; + uses acl-interfaces-top; + } + } + + // data definition statements + uses acl-top; + + // augment statements + + +} diff --git a/models/yang/openconfig-if-ethernet.yang b/models/yang/openconfig-if-ethernet.yang new file mode 100644 index 000000000..e917bba4d --- /dev/null +++ b/models/yang/openconfig-if-ethernet.yang @@ -0,0 +1,438 @@ +module openconfig-if-ethernet { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces/ethernet"; + + prefix "oc-eth"; + + // import some basic types + import openconfig-interfaces { prefix oc-if; } + import iana-if-type { prefix ift; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "Model for managing Ethernet interfaces -- augments the OpenConfig + model for interface configuration and state."; + + oc-ext:openconfig-version "2.6.2"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "2.6.2"; + } + + revision "2018-09-04" { + description + "Remove in-crc-align-errors as it is a duplicate of + in-crc-errors"; + reference "2.6.1"; + } + + revision "2018-08-28" { + description + "Add Ethernet counter in-block-errors"; + reference "2.6.0"; + } + + revision "2018-07-02" { + description + "Add new ethernet counters of in-undersize-frames, + in-crc-align-errors and the distribution container"; + reference "2.5.0"; + } + + revision "2018-04-10" { + description + "Add identities for 2.5 and 5 Gbps."; + reference "2.4.0"; + } + + revision "2018-01-05" { + description + "Add logical loopback to interface."; + reference "2.3.0"; + } + + revision "2017-12-21" { + description + "Added IPv6 router advertisement configuration."; + reference "2.1.0"; + } + + revision "2017-07-14" { + description + "Added Ethernet/IP state data; Add dhcp-client; + migrate to OpenConfig types modules; Removed or + renamed opstate values"; + reference "2.0.0"; + } + + revision "2016-12-22" { + description + "Fixes to Ethernet interfaces model"; + reference "1.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + + identity ETHERNET_SPEED { + description "base type to specify available Ethernet link + speeds"; + } + + identity SPEED_10MB { + base ETHERNET_SPEED; + description "10 Mbps Ethernet"; + } + + identity SPEED_100MB { + base ETHERNET_SPEED; + description "100 Mbps Ethernet"; + } + + identity SPEED_1GB { + base ETHERNET_SPEED; + description "1 Gbps Ethernet"; + } + + identity SPEED_2500MB { + base ETHERNET_SPEED; + description "2.5 Gbps Ethernet"; + } + + identity SPEED_5GB { + base ETHERNET_SPEED; + description "5 Gbps Ethernet"; + } + + identity SPEED_10GB { + base ETHERNET_SPEED; + description "10 Gbps Ethernet"; + } + + identity SPEED_25GB { + base ETHERNET_SPEED; + description "25 Gbps Ethernet"; + } + + identity SPEED_40GB { + base ETHERNET_SPEED; + description "40 Gbps Ethernet"; + } + + identity SPEED_50GB { + base ETHERNET_SPEED; + description "50 Gbps Ethernet"; + } + + identity SPEED_100GB { + base ETHERNET_SPEED; + description "100 Gbps Ethernet"; + } + + identity SPEED_UNKNOWN { + base ETHERNET_SPEED; + description + "Interface speed is unknown. Systems may report + speed UNKNOWN when an interface is down or unpopuplated (e.g., + pluggable not present)."; + } + + // typedef statements + + + // grouping statements + + grouping ethernet-interface-config { + description "Configuration items for Ethernet interfaces"; + + leaf mac-address { + type oc-yang:mac-address; + description + "Assigns a MAC address to the Ethernet interface. If not + specified, the corresponding operational state leaf is + expected to show the system-assigned MAC address."; + } + + leaf auto-negotiate { + type boolean; + default true; + description + "Set to TRUE to request the interface to auto-negotiate + transmission parameters with its peer interface. When + set to FALSE, the transmission parameters are specified + manually."; + reference + "IEEE 802.3-2012 auto-negotiation transmission parameters"; + } + + leaf duplex-mode { + type enumeration { + enum FULL { + description "Full duplex mode"; + } + enum HALF { + description "Half duplex mode"; + } + } + description + "When auto-negotiate is TRUE, this optionally sets the + duplex mode that will be advertised to the peer. If + unspecified, the interface should negotiate the duplex mode + directly (typically full-duplex). When auto-negotiate is + FALSE, this sets the duplex mode on the interface directly."; + } + + leaf port-speed { + type identityref { + base ETHERNET_SPEED; + } + description + "When auto-negotiate is TRUE, this optionally sets the + port-speed mode that will be advertised to the peer for + negotiation. If unspecified, it is expected that the + interface will select the highest speed available based on + negotiation. When auto-negotiate is set to FALSE, sets the + link speed to a fixed value -- supported values are defined + by ETHERNET_SPEED identities"; + } + + leaf enable-flow-control { + type boolean; + default false; + description + "Enable or disable flow control for this interface. + Ethernet flow control is a mechanism by which a receiver + may send PAUSE frames to a sender to stop transmission for + a specified time. + + This setting should override auto-negotiated flow control + settings. If left unspecified, and auto-negotiate is TRUE, + flow control mode is negotiated with the peer interface."; + reference + "IEEE 802.3x"; + } + } + + grouping ethernet-interface-state-counters { + description + "Ethernet-specific counters and statistics"; + + // ingress counters + + leaf in-mac-control-frames { + type oc-yang:counter64; + description + "MAC layer control frames received on the interface"; + } + + leaf in-mac-pause-frames { + type oc-yang:counter64; + description + "MAC layer PAUSE frames received on the interface"; + } + + leaf in-oversize-frames { + type oc-yang:counter64; + description + "The total number of frames received that were + longer than 1518 octets (excluding framing bits, + but including FCS octets) and were otherwise + well formed."; + } + + leaf in-undersize-frames { + type oc-yang:counter64; + description + "The total number of frames received that were + less than 64 octets long (excluding framing bits, + but including FCS octets) and were otherwise well + formed."; + reference + "RFC 2819: Remote Network Monitoring MIB - + etherStatsUndersizePkts"; + } + + leaf in-jabber-frames { + type oc-yang:counter64; + description + "Number of jabber frames received on the + interface. Jabber frames are typically defined as oversize + frames which also have a bad CRC. Implementations may use + slightly different definitions of what constitutes a jabber + frame. Often indicative of a NIC hardware problem."; + } + + leaf in-fragment-frames { + type oc-yang:counter64; + description + "The total number of frames received that were less than + 64 octets in length (excluding framing bits but including + FCS octets) and had either a bad Frame Check Sequence + (FCS) with an integral number of octets (FCS Error) or a + bad FCS with a non-integral number of octets (Alignment + Error)."; + } + + leaf in-8021q-frames { + type oc-yang:counter64; + description + "Number of 802.1q tagged frames received on the interface"; + } + + leaf in-crc-errors { + type oc-yang:counter64; + description + "The total number of frames received that + had a length (excluding framing bits, but + including FCS octets) of between 64 and 1518 + octets, inclusive, but had either a bad + Frame Check Sequence (FCS) with an integral + number of octets (FCS Error) or a bad FCS with + a non-integral number of octets (Alignment Error)"; + reference + "RFC 2819: Remote Network Monitoring MIB - + etherStatsCRCAlignErrors"; + } + + leaf in-block-errors { + type oc-yang:counter64; + description + "The number of received errored blocks. Error detection codes + are capable of detecting whether one or more errors have + occurred in a given sequence of bits – the block. It is + normally not possible to determine the exact number of errored + bits within the block"; + } + + // egress counters + + leaf out-mac-control-frames { + type oc-yang:counter64; + description + "MAC layer control frames sent on the interface"; + } + + leaf out-mac-pause-frames { + type oc-yang:counter64; + description + "MAC layer PAUSE frames sent on the interface"; + } + + leaf out-8021q-frames { + type oc-yang:counter64; + description + "Number of 802.1q tagged frames sent on the interface"; + } + } + + grouping ethernet-interface-state { + description + "Grouping for defining Ethernet-specific operational state"; + + leaf hw-mac-address { + type oc-yang:mac-address; + description + "Represenets the 'burned-in', or system-assigned, MAC + address for the Ethernet interface."; + } + + leaf negotiated-duplex-mode { + type enumeration { + enum FULL { + description "Full duplex mode"; + } + enum HALF { + description "Half duplex mode"; + } + } + description + "When auto-negotiate is set to TRUE, and the interface has + completed auto-negotiation with the remote peer, this value + shows the duplex mode that has been negotiated."; + } + + leaf negotiated-port-speed { + type identityref { + base ETHERNET_SPEED; + } + description + "When auto-negotiate is set to TRUE, and the interface has + completed auto-negotiation with the remote peer, this value + shows the interface speed that has been negotiated."; + } + + container counters { + description "Ethernet interface counters"; + + uses ethernet-interface-state-counters; + + } + + } + + // data definition statements + + grouping ethernet-top { + description "top-level Ethernet config and state containers"; + + container ethernet { + description + "Top-level container for ethernet configuration + and state"; + + container config { + description "Configuration data for ethernet interfaces"; + + uses ethernet-interface-config; + + } + + container state { + + config false; + description "State variables for Ethernet interfaces"; + + uses ethernet-interface-config; + uses ethernet-interface-state; + + } + + } + } + + // augment statements + + augment "/oc-if:interfaces/oc-if:interface" { + description "Adds addtional Ethernet-specific configuration to + interfaces model"; + + uses ethernet-top { + when "oc-if:state/oc-if:type = 'ift:ethernetCsmacd'" { + description "Additional interface configuration parameters when + the interface type is Ethernet"; + } + } + } + + // rpc statements + + // notification statements + +} diff --git a/models/yang/openconfig-if-ip.yang b/models/yang/openconfig-if-ip.yang new file mode 100644 index 000000000..df89662f8 --- /dev/null +++ b/models/yang/openconfig-if-ip.yang @@ -0,0 +1,1322 @@ +module openconfig-if-ip { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces/ip"; + + prefix "oc-ip"; + + // import some basic types + import openconfig-inet-types { prefix oc-inet; } + import openconfig-interfaces { prefix oc-if; } + import openconfig-vlan { prefix oc-vlan; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "This model defines data for managing configuration and + operational state on IP (IPv4 and IPv6) interfaces. + + This model reuses data items defined in the IETF YANG model for + interfaces described by RFC 7277 with an alternate structure + (particularly for operational state data) and with + additional configuration items. + + Portions of this code were derived from IETF RFC 7277. + Please reproduce this note if possible. + + IETF code is subject to the following copyright and license: + Copyright (c) IETF Trust and the persons identified as authors of + the code. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in + Section 4.c of the IETF Trust's Legal Provisions Relating + to IETF Documents (http://trustee.ietf.org/license-info)."; + + oc-ext:openconfig-version "2.3.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "2.3.1"; + } + + revision "2018-01-05" { + description + "Add logical loopback to interface."; + reference "2.3.0"; + } + + revision "2017-12-21" { + description + "Added IPv6 router advertisement configuration."; + reference "2.1.0"; + } + + revision "2017-07-14" { + description + "Added Ethernet/IP state data; Add dhcp-client; + migrate to OpenConfig types modules; Removed or + renamed opstate values"; + reference "2.0.0"; + } + + revision "2017-04-03"{ + description + "Update copyright notice."; + reference "1.1.1"; + } + + revision "2016-12-22" { + description + "Fixes to Ethernet interfaces model"; + reference "1.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // typedef statements + + typedef ip-address-origin { + type enumeration { + enum OTHER { + description + "None of the following."; + } + enum STATIC { + description + "Indicates that the address has been statically + configured - for example, using NETCONF or a Command Line + Interface."; + } + enum DHCP { + description + "Indicates an address that has been assigned to this + system by a DHCP server."; + } + enum LINK_LAYER { + description + "Indicates an address created by IPv6 stateless + autoconfiguration that embeds a link-layer address in its + interface identifier."; + } + enum RANDOM { + description + "Indicates an address chosen by the system at + random, e.g., an IPv4 address within 169.254/16, an + RFC 4941 temporary address, or an RFC 7217 semantically + opaque address."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + RFC 7217: A Method for Generating Semantically Opaque + Interface Identifiers with IPv6 Stateless + Address Autoconfiguration (SLAAC)"; + } + } + description + "The origin of an address."; + } + + typedef neighbor-origin { + type enumeration { + enum OTHER { + description + "None of the following."; + } + enum STATIC { + description + "Indicates that the mapping has been statically + configured - for example, using NETCONF or a Command Line + Interface."; + } + enum DYNAMIC { + description + "Indicates that the mapping has been dynamically resolved + using, e.g., IPv4 ARP or the IPv6 Neighbor Discovery + protocol."; + } + } + description + "The origin of a neighbor entry."; + } + + // grouping statements + + grouping ip-common-global-config { + description + "Shared configuration data for IPv4 or IPv6 assigned + globally on an interface."; + + leaf dhcp-client { + type boolean; + default false; + description + "Enables a DHCP client on the interface in order to request + an address"; + } + } + + grouping ip-common-counters-state { + description + "Operational state for IP traffic statistics for IPv4 and + IPv6"; + + container counters { + description + "Packet and byte counters for IP transmission and + reception for the address family."; + + + leaf in-pkts { + type oc-yang:counter64; + description + "The total number of IP packets received for the specified + address family, including those received in error"; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf in-octets { + type oc-yang:counter64; + description + "The total number of octets received in input IP packets + for the specified address family, including those received + in error."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf in-error-pkts { + // TODO: this counter combines several error conditions -- + // could consider breaking them out to separate leaf nodes + type oc-yang:counter64; + description + "Number of IP packets discarded due to errors for the + specified address family, including errors in the IP + header, no route found to the IP destination, invalid + address, unknown protocol, etc."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf in-forwarded-pkts { + type oc-yang:counter64; + description + "The number of input packets for which the device was not + their final IP destination and for which the device + attempted to find a route to forward them to that final + destination."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf in-forwarded-octets { + type oc-yang:counter64; + description + "The number of octets received in input IP packets + for the specified address family for which the device was + not their final IP destination and for which the + device attempted to find a route to forward them to that + final destination."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf in-discarded-pkts { + type oc-yang:counter64; + description + "The number of input IP packets for the + specified address family, for which no problems were + encountered to prevent their continued processing, but + were discarded (e.g., for lack of buffer space)."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf out-pkts { + type oc-yang:counter64; + description + "The total number of IP packets for the + specified address family that the device supplied + to the lower layers for transmission. This includes + packets generated locally and those forwarded by the + device."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf out-octets { + type oc-yang:counter64; + description + "The total number of octets in IP packets for the + specified address family that the device + supplied to the lower layers for transmission. This + includes packets generated locally and those forwarded by + the device."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf out-error-pkts { + // TODO: this counter combines several error conditions -- + // could consider breaking them out to separate leaf nodes + type oc-yang:counter64; + description + "Number of IP packets for the specified address family + locally generated and discarded due to errors, including + no route found to the IP destination."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf out-forwarded-pkts { + type oc-yang:counter64; + description + "The number of packets for which this entity was not their + final IP destination and for which it was successful in + finding a path to their final destination."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf out-forwarded-octets { + type oc-yang:counter64; + description + "The number of octets in packets for which this entity was + not their final IP destination and for which it was + successful in finding a path to their final destination."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + + leaf out-discarded-pkts { + type oc-yang:counter64; + description + "The number of output IP packets for the + specified address family for which no problem was + encountered to prevent their transmission to their + destination, but were discarded (e.g., for lack of + buffer space)."; + reference + "RFC 4293 - Management Information Base for the + Internet Protocol (IP)"; + } + } + + } + + + + grouping ipv4-global-config { + description + "Configuration data for IPv4 interfaces across + all addresses assigned to the interface"; + + leaf enabled { + type boolean; + default true; + description + "Controls whether IPv4 is enabled or disabled on this + interface. When IPv4 is enabled, this interface is + connected to an IPv4 stack, and the interface can send + and receive IPv4 packets."; + } + + leaf mtu { + type uint16 { + range "68..max"; + } + units octets; + description + "The size, in octets, of the largest IPv4 packet that the + interface will send and receive. + + The server may restrict the allowed values for this leaf, + depending on the interface's type. + + If this leaf is not configured, the operationally used MTU + depends on the interface's type."; + reference + "RFC 791: Internet Protocol"; + } + + uses ip-common-global-config; + + + } + + grouping ipv4-address-config { + + description + "Per IPv4 adresss configuration data for the + interface."; + + leaf ip { + type oc-inet:ipv4-address; + description + "The IPv4 address on the interface."; + } + + leaf prefix-length { + type uint8 { + range "0..32"; + } + description + "The length of the subnet prefix."; + } + } + + grouping ipv4-neighbor-config { + description + "Per IPv4 neighbor configuration data. Neighbor + entries are analagous to static ARP entries, i.e., they + create a correspondence between IP and link-layer addresses"; + + leaf ip { + type oc-inet:ipv4-address; + description + "The IPv4 address of the neighbor node."; + } + leaf link-layer-address { + type oc-yang:phys-address; + mandatory true; + description + "The link-layer address of the neighbor node."; + } + } + + grouping ipv4-address-state { + description + "State variables for IPv4 addresses on the interface"; + + leaf origin { + type ip-address-origin; + description + "The origin of this address, e.g., statically configured, + assigned by DHCP, etc.."; + } + } + + grouping ipv4-neighbor-state { + description + "State variables for IPv4 neighbor entries on the interface."; + + leaf origin { + type neighbor-origin; + description + "The origin of this neighbor entry, static or dynamic."; + } + } + + grouping ipv6-global-config { + description + "Configuration data at the global level for each + IPv6 interface"; + + leaf enabled { + type boolean; + default true; + description + "Controls whether IPv6 is enabled or disabled on this + interface. When IPv6 is enabled, this interface is + connected to an IPv6 stack, and the interface can send + and receive IPv6 packets."; + } + + leaf mtu { + type uint32 { + range "1280..max"; + } + units octets; + description + "The size, in octets, of the largest IPv6 packet that the + interface will send and receive. + + The server may restrict the allowed values for this leaf, + depending on the interface's type. + + If this leaf is not configured, the operationally used MTU + depends on the interface's type."; + reference + "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + Section 5"; + } + + leaf dup-addr-detect-transmits { + type uint32; + default 1; + description + "The number of consecutive Neighbor Solicitation messages + sent while performing Duplicate Address Detection on a + tentative address. A value of zero indicates that + Duplicate Address Detection is not performed on + tentative addresses. A value of one indicates a single + transmission with no follow-up retransmissions."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration"; + } + + uses ip-common-global-config; + } + + grouping ipv6-address-config { + description "Per-address configuration data for IPv6 interfaces"; + + leaf ip { + type oc-inet:ipv6-address; + description + "The IPv6 address on the interface."; + } + + leaf prefix-length { + type uint8 { + range "0..128"; + } + mandatory true; + description + "The length of the subnet prefix."; + } + } + + grouping ipv6-address-state { + description + "Per-address operational state data for IPv6 interfaces"; + + leaf origin { + type ip-address-origin; + description + "The origin of this address, e.g., static, dhcp, etc."; + } + + leaf status { + type enumeration { + enum PREFERRED { + description + "This is a valid address that can appear as the + destination or source address of a packet."; + } + enum DEPRECATED { + description + "This is a valid but deprecated address that should + no longer be used as a source address in new + communications, but packets addressed to such an + address are processed as expected."; + } + enum INVALID { + description + "This isn't a valid address, and it shouldn't appear + as the destination or source address of a packet."; + } + enum INACCESSIBLE { + description + "The address is not accessible because the interface + to which this address is assigned is not + operational."; + } + enum UNKNOWN { + description + "The status cannot be determined for some reason."; + } + enum TENTATIVE { + description + "The uniqueness of the address on the link is being + verified. Addresses in this state should not be + used for general communication and should only be + used to determine the uniqueness of the address."; + } + enum DUPLICATE { + description + "The address has been determined to be non-unique on + the link and so must not be used."; + } + enum OPTIMISTIC { + description + "The address is available for use, subject to + restrictions, while its uniqueness on a link is + being verified."; + } + } + description + "The status of an address. Most of the states correspond + to states from the IPv6 Stateless Address + Autoconfiguration protocol."; + reference + "RFC 4293: Management Information Base for the + Internet Protocol (IP) + - IpAddressStatusTC + RFC 4862: IPv6 Stateless Address Autoconfiguration"; + } + } + + grouping ipv6-neighbor-config { + description + "Per-neighbor configuration data for IPv6 interfaces"; + + leaf ip { + type oc-inet:ipv6-address; + description + "The IPv6 address of the neighbor node."; + } + + leaf link-layer-address { + type oc-yang:phys-address; + mandatory true; + description + "The link-layer address of the neighbor node."; + } + } + + grouping ipv6-neighbor-state { + description "Per-neighbor state variables for IPv6 interfaces"; + + leaf origin { + type neighbor-origin; + description + "The origin of this neighbor entry."; + } + leaf is-router { + type empty; + description + "Indicates that the neighbor node acts as a router."; + } + leaf neighbor-state { + type enumeration { + enum INCOMPLETE { + description + "Address resolution is in progress, and the link-layer + address of the neighbor has not yet been + determined."; + } + enum REACHABLE { + description + "Roughly speaking, the neighbor is known to have been + reachable recently (within tens of seconds ago)."; + } + enum STALE { + description + "The neighbor is no longer known to be reachable, but + until traffic is sent to the neighbor no attempt + should be made to verify its reachability."; + } + enum DELAY { + description + "The neighbor is no longer known to be reachable, and + traffic has recently been sent to the neighbor. + Rather than probe the neighbor immediately, however, + delay sending probes for a short while in order to + give upper-layer protocols a chance to provide + reachability confirmation."; + } + enum PROBE { + description + "The neighbor is no longer known to be reachable, and + unicast Neighbor Solicitation probes are being sent + to verify reachability."; + } + } + description + "The Neighbor Unreachability Detection state of this + entry."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) + Section 7.3.2"; + } + } + + grouping ip-vrrp-ipv6-config { + description + "IPv6-specific configuration data for VRRP on IPv6 + interfaces"; + + leaf virtual-link-local { + type oc-inet:ip-address; + description + "For VRRP on IPv6 interfaces, sets the virtual link local + address"; + } + } + + grouping ip-vrrp-ipv6-state { + description + "IPv6-specific operational state for VRRP on IPv6 interfaces"; + + uses ip-vrrp-ipv6-config; + } + + grouping ip-vrrp-tracking-config { + description + "Configuration data for tracking interfaces + in a VRRP group"; + + leaf-list track-interface { + type leafref { + path "/oc-if:interfaces/oc-if:interface/oc-if:name"; + } + // TODO: we may need to add some restriction to ethernet + // or IP interfaces. + description + "Sets a list of one or more interfaces that should + be tracked for up/down events to dynamically change the + priority state of the VRRP group, and potentially + change the mastership if the tracked interface going + down lowers the priority sufficiently. Any of the tracked + interfaces going down will cause the priority to be lowered. + Some implementations may only support a single + tracked interface."; + } + + leaf priority-decrement { + type uint8 { + range 0..254; + } + default 0; + description "Set the value to subtract from priority when + the tracked interface goes down"; + } + } + + grouping ip-vrrp-tracking-state { + description + "Operational state data for tracking interfaces in a VRRP + group"; + } + + grouping ip-vrrp-tracking-top { + description + "Top-level grouping for VRRP interface tracking"; + + container interface-tracking { + description + "Top-level container for VRRP interface tracking"; + + container config { + description + "Configuration data for VRRP interface tracking"; + + uses ip-vrrp-tracking-config; + } + + container state { + + config false; + + description + "Operational state data for VRRP interface tracking"; + + uses ip-vrrp-tracking-config; + uses ip-vrrp-tracking-state; + } + } + } + + grouping ip-vrrp-config { + description + "Configuration data for VRRP on IP interfaces"; + + leaf virtual-router-id { + type uint8 { + range 1..255; + } + description + "Set the virtual router id for use by the VRRP group. This + usually also determines the virtual MAC address that is + generated for the VRRP group"; + } + + leaf-list virtual-address { + type oc-inet:ip-address; + description + "Configure one or more virtual addresses for the + VRRP group"; + } + + leaf priority { + type uint8 { + range 1..254; + } + default 100; + description + "Specifies the sending VRRP interface's priority + for the virtual router. Higher values equal higher + priority"; + } + + leaf preempt { + type boolean; + default true; + description + "When set to true, enables preemption by a higher + priority backup router of a lower priority master router"; + } + + leaf preempt-delay { + type uint16 { + range 0..3600; + } + default 0; + description + "Set the delay the higher priority router waits + before preempting"; + } + + leaf accept-mode { + type boolean; + // TODO: should we adopt the RFC default given the common + // operational practice of setting to true? + default false; + description + "Configure whether packets destined for + virtual addresses are accepted even when the virtual + address is not owned by the router interface"; + } + + leaf advertisement-interval { + type uint16 { + range 1..4095; + } + // TODO this range is theoretical -- needs to be validated + // against major implementations. + units "centiseconds"; + default 100; + description + "Sets the interval between successive VRRP + advertisements -- RFC 5798 defines this as a 12-bit + value expressed as 0.1 seconds, with default 100, i.e., + 1 second. Several implementation express this in units of + seconds"; + } + } + + grouping ip-vrrp-state { + description + "Operational state data for VRRP on IP interfaces"; + + leaf current-priority { + type uint8; + description "Operational value of the priority for the + interface in the VRRP group"; + } + } + + grouping ip-vrrp-top { + description + "Top-level grouping for Virtual Router Redundancy Protocol"; + + container vrrp { + description + "Enclosing container for VRRP groups handled by this + IP interface"; + + reference "RFC 5798 - Virtual Router Redundancy Protocol + (VRRP) Version 3 for IPv4 and IPv6"; + + list vrrp-group { + key "virtual-router-id"; + description + "List of VRRP groups, keyed by virtual router id"; + + leaf virtual-router-id { + type leafref { + path "../config/virtual-router-id"; + } + description + "References the configured virtual router id for this + VRRP group"; + } + + container config { + description + "Configuration data for the VRRP group"; + + uses ip-vrrp-config; + } + + container state { + + config false; + + description + "Operational state data for the VRRP group"; + + uses ip-vrrp-config; + uses ip-vrrp-state; + } + + uses ip-vrrp-tracking-top; + } + } + } + + grouping ipv6-ra-config { + description + "Configuration parameters for IPv6 router advertisements."; + + leaf interval { + type uint32; + units seconds; + description + "The interval between periodic router advertisement neighbor + discovery messages sent on this interface expressed in + seconds."; + } + + leaf lifetime { + type uint32; + units seconds; + description + "The lifetime advertised in the router advertisement neighbor + discovery message on this interface."; + } + + leaf suppress { + type boolean; + default false; + description + "When set to true, router advertisement neighbor discovery + messages are not transmitted on this interface."; + } + } + + grouping ipv4-proxy-arp-config { + description + "Configuration parameters for IPv4 proxy ARP"; + + leaf mode { + type enumeration { + enum DISABLE { + description + "The system should not respond to ARP requests that + do not specify an IP address configured on the local + subinterface as the target address."; + } + enum REMOTE_ONLY { + description + "The system responds to ARP requests only when the + sender and target IP addresses are in different + subnets."; + } + enum ALL { + description + "The system responds to ARP requests where the sender + and target IP addresses are in different subnets, as well + as those where they are in the same subnet."; + } + } + default "DISABLE"; + description + "When set to a value other than DISABLE, the local system should + respond to ARP requests that are for target addresses other than + those that are configured on the local subinterface using its own + MAC address as the target hardware address. If the REMOTE_ONLY + value is specified, replies are only sent when the target address + falls outside the locally configured subnets on the interface, + whereas with the ALL value, all requests, regardless of their + target address are replied to."; + reference "RFC1027: Using ARP to Implement Transparent Subnet Gateways"; + } + } + + grouping ipv4-top { + description "Top-level configuration and state for IPv4 + interfaces"; + + container ipv4 { + description + "Parameters for the IPv4 address family."; + + container addresses { + description + "Enclosing container for address list"; + + list address { + key "ip"; + description + "The list of configured IPv4 addresses on the interface."; + + leaf ip { + type leafref { + path "../config/ip"; + } + description "References the configured IP address"; + } + + container config { + description "Configuration data for each configured IPv4 + address on the interface"; + + uses ipv4-address-config; + + } + + container state { + + config false; + description "Operational state data for each IPv4 address + configured on the interface"; + + uses ipv4-address-config; + uses ipv4-address-state; + } + + } + } + + container proxy-arp { + description + "Configuration and operational state parameters + relating to proxy ARP. This functionality allows a + system to respond to ARP requests that are not + explicitly destined to the local system."; + + container config { + description + "Configuration parameters for proxy ARP"; + uses ipv4-proxy-arp-config; + } + + container state { + config false; + description + "Operational state parameters for proxy ARP"; + uses ipv4-proxy-arp-config; + } + } + + container neighbors { + description + "Enclosing container for neighbor list"; + + list neighbor { + key "ip"; + description + "A list of mappings from IPv4 addresses to + link-layer addresses. + + Entries in this list are used as static entries in the + ARP Cache."; + reference + "RFC 826: An Ethernet Address Resolution Protocol"; + + leaf ip { + type leafref { + path "../config/ip"; + } + description "References the configured IP address"; + } + + container config { + description "Configuration data for each configured IPv4 + address on the interface"; + + uses ipv4-neighbor-config; + + } + + container state { + + config false; + description "Operational state data for each IPv4 address + configured on the interface"; + + uses ipv4-neighbor-config; + uses ipv4-neighbor-state; + } + } + } + + uses oc-if:sub-unnumbered-top; + + container config { + description + "Top-level IPv4 configuration data for the interface"; + + uses ipv4-global-config; + } + + container state { + + config false; + description + "Top level IPv4 operational state data"; + + uses ipv4-global-config; + uses ip-common-counters-state; + } + } + } + + grouping ipv6-top { + description + "Top-level configuration and state for IPv6 interfaces"; + + container ipv6 { + description + "Parameters for the IPv6 address family."; + + container addresses { + description + "Enclosing container for address list"; + + list address { + key "ip"; + description + "The list of configured IPv6 addresses on the interface."; + + leaf ip { + type leafref { + path "../config/ip"; + } + description "References the configured IP address"; + } + + container config { + description + "Configuration data for each IPv6 address on + the interface"; + + uses ipv6-address-config; + + } + + container state { + + config false; + description + "State data for each IPv6 address on the + interface"; + + uses ipv6-address-config; + uses ipv6-address-state; + } + } + } + + container router-advertisement { + description + "Configuration and operational state parameters relating to + router advertisements."; + + container config { + description + "Configuration parameters relating to router advertisements + for IPv6."; + uses ipv6-ra-config; + } + + container state { + config false; + description + "Operational state parameters relating to router + advertisements for IPv6."; + uses ipv6-ra-config; + } + } + + container neighbors { + description + "Enclosing container for list of IPv6 neighbors"; + + list neighbor { + key "ip"; + description + "List of IPv6 neighbors"; + + leaf ip { + type leafref { + path "../config/ip"; + } + description + "References the configured IP neighbor address"; + } + + container config { + description "Configuration data for each IPv6 address on + the interface"; + + uses ipv6-neighbor-config; + + } + + container state { + + config false; + description "State data for each IPv6 address on the + interface"; + + uses ipv6-neighbor-config; + uses ipv6-neighbor-state; + } + } + } + uses oc-if:sub-unnumbered-top; + + container config { + description "Top-level config data for the IPv6 interface"; + + uses ipv6-global-config; + } + + container state { + config false; + description + "Top-level operational state data for the IPv6 interface"; + + uses ipv6-global-config; + uses ip-common-counters-state; + + } + } + } + + // augment statements + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface" { + description + "IPv4 address family configuration for + interfaces"; + + uses ipv4-top; + + } + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface" { + description + "IPv6 address family configuration for + interfaces"; + + uses ipv6-top; + + } + + // VRRP for IPv4 interfaces + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface/oc-ip:ipv4/oc-ip:addresses/oc-ip:address" { + + description + "Additional IP addr family configuration for + interfaces"; + + uses ip-vrrp-top; + + } + + // VRRP for IPv6 interfaces + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface/oc-ip:ipv6/oc-ip:addresses/oc-ip:address" { + description + "Additional IP addr family configuration for + interfaces"; + + uses ip-vrrp-top; + + } + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface/oc-ip:ipv6/oc-ip:addresses/oc-ip:address/" + + "vrrp/vrrp-group/config" { + description + "Additional VRRP data for IPv6 interfaces"; + + uses ip-vrrp-ipv6-config; + } + + augment "/oc-if:interfaces/oc-if:interface/oc-if:subinterfaces/" + + "oc-if:subinterface/oc-ip:ipv6/oc-ip:addresses/oc-ip:address/vrrp/" + + "vrrp-group/state" { + description + "Additional VRRP data for IPv6 interfaces"; + + uses ip-vrrp-ipv6-state; + } + + // Augments for for routed VLANs + + augment "/oc-if:interfaces/oc-if:interface/oc-vlan:routed-vlan" { + description + "IPv4 address family configuration for + interfaces"; + + uses ipv4-top; + } + + augment "/oc-if:interfaces/oc-if:interface/oc-vlan:routed-vlan" { + description + "IPv6 address family configuration for + interfaces"; + + uses ipv6-top; + } + + // VRRP for routed VLAN interfaces + + augment "/oc-if:interfaces/oc-if:interface/oc-vlan:routed-vlan/" + + "oc-ip:ipv4/oc-ip:addresses/oc-ip:address" { + description + "Additional IP addr family configuration for + interfaces"; + + uses ip-vrrp-top; + + } + + augment "/oc-if:interfaces/oc-if:interface/oc-vlan:routed-vlan/" + + "oc-ip:ipv6/oc-ip:addresses/oc-ip:address" { + description + "Additional IP addr family configuration for + interfaces"; + + uses ip-vrrp-top; + + } + + augment "/oc-if:interfaces/oc-if:interface/oc-vlan:routed-vlan/" + + "oc-ip:ipv6/oc-ip:addresses/oc-ip:address/vrrp/vrrp-group/config" { + description + "Additional VRRP data for IPv6 interfaces"; + + uses ip-vrrp-ipv6-config; + } + + + augment "/oc-if:interfaces/oc-if:interface/oc-vlan:routed-vlan/" + + "oc-ip:ipv6/oc-ip:addresses/oc-ip:address/vrrp/vrrp-group/state" { + description + "Additional VRRP data for IPv6 interfaces"; + + uses ip-vrrp-ipv6-state; + } + + // rpc statements + + // notification statements +} diff --git a/models/yang/openconfig-interfaces.yang b/models/yang/openconfig-interfaces.yang new file mode 100644 index 000000000..f3e0feeac --- /dev/null +++ b/models/yang/openconfig-interfaces.yang @@ -0,0 +1,1067 @@ +module openconfig-interfaces { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/interfaces"; + + prefix "oc-if"; + + // import some basic types + import ietf-interfaces { prefix ietf-if; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-types { prefix oc-types; } + import openconfig-extensions { prefix oc-ext; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "Model for managing network interfaces and subinterfaces. This + module also defines convenience types / groupings for other + models to create references to interfaces: + + base-interface-ref (type) - reference to a base interface + interface-ref (grouping) - container for reference to a + interface + subinterface + interface-ref-state (grouping) - container for read-only + (opstate) reference to interface + subinterface + + This model reuses data items defined in the IETF YANG model for + interfaces described by RFC 7223 with an alternate structure + (particularly for operational state data) and with + additional configuration items. + + Portions of this code were derived from IETF RFC 7223. + Please reproduce this note if possible. + + IETF code is subject to the following copyright and license: + Copyright (c) IETF Trust and the persons identified as authors of + the code. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in + Section 4.c of the IETF Trust's Legal Provisions Relating + to IETF Documents (http://trustee.ietf.org/license-info)."; + + oc-ext:openconfig-version "2.4.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "2.4.1"; + } + + revision "2018-08-07" { + description + "Add leaf to indicate whether an interface is physical or + logical."; + reference "2.4.0"; + } + + revision "2018-07-02" { + description + "Add in-pkts and out-pkts in counters"; + reference "2.3.2"; + } + + revision "2018-04-24" { + description + "Clarified behavior of last-change state leaf"; + reference "2.3.1"; + } + + revision "2018-01-05" { + description + "Add logical loopback to interface."; + reference "2.3.0"; + } + + revision "2017-12-22" { + description + "Add IPv4 proxy ARP configuration."; + reference "2.2.0"; + } + + revision "2017-12-21" { + description + "Added IPv6 router advertisement configuration."; + reference "2.1.0"; + } + + revision "2017-07-14" { + description + "Added Ethernet/IP state data; Add dhcp-client; + migrate to OpenConfig types modules; Removed or + renamed opstate values"; + reference "2.0.0"; + } + + revision "2017-04-03" { + description + "Update copyright notice."; + reference "1.1.1"; + } + + revision "2016-12-22" { + description + "Fixes to Ethernet interfaces model"; + reference "1.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // typedef statements + + typedef base-interface-ref { + type leafref { + path "/oc-if:interfaces/oc-if:interface/oc-if:name"; + } + description + "Reusable type for by-name reference to a base interface. + This type may be used in cases where ability to reference + a subinterface is not required."; + } + + typedef interface-id { + type string; + description + "User-defined identifier for an interface, generally used to + name a interface reference. The id can be arbitrary but a + useful convention is to use a combination of base interface + name and subinterface index."; + } + + // grouping statements + + grouping interface-ref-common { + description + "Reference leafrefs to interface / subinterface"; + + leaf interface { + type leafref { + path "/oc-if:interfaces/oc-if:interface/oc-if:name"; + } + description + "Reference to a base interface. If a reference to a + subinterface is required, this leaf must be specified + to indicate the base interface."; + } + + leaf subinterface { + type leafref { + path "/oc-if:interfaces/" + + "oc-if:interface[oc-if:name=current()/../interface]/" + + "oc-if:subinterfaces/oc-if:subinterface/oc-if:index"; + } + description + "Reference to a subinterface -- this requires the base + interface to be specified using the interface leaf in + this container. If only a reference to a base interface + is requuired, this leaf should not be set."; + } + } + + grouping interface-ref-state-container { + description + "Reusable opstate w/container for a reference to an + interface or subinterface"; + + container state { + config false; + description + "Operational state for interface-ref"; + + uses interface-ref-common; + } + } + + grouping interface-ref { + description + "Reusable definition for a reference to an interface or + subinterface"; + + container interface-ref { + description + "Reference to an interface or subinterface"; + + container config { + description + "Configured reference to interface / subinterface"; + oc-ext:telemetry-on-change; + + uses interface-ref-common; + } + + uses interface-ref-state-container; + } + } + + grouping interface-ref-state { + description + "Reusable opstate w/container for a reference to an + interface or subinterface"; + + container interface-ref { + description + "Reference to an interface or subinterface"; + + uses interface-ref-state-container; + } + } + + grouping base-interface-ref-state { + description + "Reusable opstate w/container for a reference to a + base interface (no subinterface)."; + + container state { + config false; + description + "Operational state for base interface reference"; + + leaf interface { + type base-interface-ref; + description + "Reference to a base interface."; + } + } + } + + + grouping interface-common-config { + description + "Configuration data data nodes common to physical interfaces + and subinterfaces"; + + leaf description { + type string; + description + "A textual description of the interface. + + A server implementation MAY map this leaf to the ifAlias + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifAlias. The definition of + such a mechanism is outside the scope of this document. + + Since ifAlias is defined to be stored in non-volatile + storage, the MIB implementation MUST map ifAlias to the + value of 'description' in the persistently stored + datastore. + + Specifically, if the device supports ':startup', when + ifAlias is read the device MUST return the value of + 'description' in the 'startup' datastore, and when it is + written, it MUST be written to the 'running' and 'startup' + datastores. Note that it is up to the implementation to + + decide whether to modify this single leaf in 'startup' or + perform an implicit copy-config from 'running' to + 'startup'. + + If the device does not support ':startup', ifAlias MUST + be mapped to the 'description' leaf in the 'running' + datastore."; + reference + "RFC 2863: The Interfaces Group MIB - ifAlias"; + } + + leaf enabled { + type boolean; + default "true"; + description + "This leaf contains the configured, desired state of the + interface. + + Systems that implement the IF-MIB use the value of this + leaf in the 'running' datastore to set + IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry + has been initialized, as described in RFC 2863. + + Changes in this leaf in the 'running' datastore are + reflected in ifAdminStatus, but if ifAdminStatus is + changed over SNMP, this leaf is not affected."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + } + + } + + grouping interface-phys-config { + description + "Configuration data for physical interfaces"; + + leaf name { + type string; + description + "The name of the interface. + + A device MAY restrict the allowed values for this leaf, + possibly depending on the type of the interface. + For system-controlled interfaces, this leaf is the + device-specific name of the interface. The 'config false' + list interfaces/interface[name]/state contains the currently + existing interfaces on the device. + + If a client tries to create configuration for a + system-controlled interface that is not present in the + corresponding state list, the server MAY reject + the request if the implementation does not support + pre-provisioning of interfaces or if the name refers to + an interface that can never exist in the system. A + NETCONF server MUST reply with an rpc-error with the + error-tag 'invalid-value' in this case. + + The IETF model in RFC 7223 provides YANG features for the + following (i.e., pre-provisioning and arbitrary-names), + however they are omitted here: + + If the device supports pre-provisioning of interface + configuration, the 'pre-provisioning' feature is + advertised. + + If the device allows arbitrarily named user-controlled + interfaces, the 'arbitrary-names' feature is advertised. + + When a configured user-controlled interface is created by + the system, it is instantiated with the same name in the + /interfaces/interface[name]/state list."; + } + + leaf type { + type identityref { + base ietf-if:interface-type; + } + mandatory true; + description + "The type of the interface. + + When an interface entry is created, a server MAY + initialize the type leaf with a valid value, e.g., if it + is possible to derive the type from the name of the + interface. + + If a client tries to set the type of an interface to a + value that can never be used by the system, e.g., if the + type is not supported or if the type does not match the + name of the interface, the server MUST reject the request. + A NETCONF server MUST reply with an rpc-error with the + error-tag 'invalid-value' in this case."; + reference + "RFC 2863: The Interfaces Group MIB - ifType"; + } + + leaf mtu { + type uint16; + description + "Set the max transmission unit size in octets + for the physical interface. If this is not set, the mtu is + set to the operational default -- e.g., 1514 bytes on an + Ethernet interface."; + } + + leaf loopback-mode { + type boolean; + default false; + description + "When set to true, the interface is logically looped back, + such that packets that are forwarded via the interface + are received on the same interface."; + } + + uses interface-common-config; + } + + grouping interface-phys-holdtime-config { + description + "Configuration data for interface hold-time settings -- + applies to physical interfaces."; + + leaf up { + type uint32; + units milliseconds; + default 0; + description + "Dampens advertisement when the interface + transitions from down to up. A zero value means dampening + is turned off, i.e., immediate notification."; + } + + leaf down { + type uint32; + units milliseconds; + default 0; + description + "Dampens advertisement when the interface transitions from + up to down. A zero value means dampening is turned off, + i.e., immediate notification."; + } + } + + grouping interface-phys-holdtime-state { + description + "Operational state data for interface hold-time."; + } + + grouping interface-phys-holdtime-top { + description + "Top-level grouping for setting link transition + dampening on physical and other types of interfaces."; + + container hold-time { + description + "Top-level container for hold-time settings to enable + dampening advertisements of interface transitions."; + + container config { + description + "Configuration data for interface hold-time settings."; + oc-ext:telemetry-on-change; + + uses interface-phys-holdtime-config; + } + + container state { + + config false; + + description + "Operational state data for interface hold-time."; + + uses interface-phys-holdtime-config; + uses interface-phys-holdtime-state; + } + } + } + + grouping interface-common-state { + description + "Operational state data (in addition to intended configuration) + at the global level for this interface"; + + oc-ext:operational; + + leaf ifindex { + type uint32; + description + "System assigned number for each interface. Corresponds to + ifIndex object in SNMP Interface MIB"; + reference + "RFC 2863 - The Interfaces Group MIB"; + oc-ext:telemetry-on-change; + } + + leaf admin-status { + type enumeration { + enum UP { + description + "Ready to pass packets."; + } + enum DOWN { + description + "Not ready to pass packets and not in some test mode."; + } + enum TESTING { + //TODO: This is generally not supported as a configured + //admin state, though it's in the standard interfaces MIB. + //Consider removing it. + description + "In some test mode."; + } + } + //TODO:consider converting to an identity to have the + //flexibility to remove some values defined by RFC 7223 that + //are not used or not implemented consistently. + mandatory true; + description + "The desired state of the interface. In RFC 7223 this leaf + has the same read semantics as ifAdminStatus. Here, it + reflects the administrative state as set by enabling or + disabling the interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + oc-ext:telemetry-on-change; + } + + leaf oper-status { + type enumeration { + enum UP { + value 1; + description + "Ready to pass packets."; + } + enum DOWN { + value 2; + description + "The interface does not pass any packets."; + } + enum TESTING { + value 3; + description + "In some test mode. No operational packets can + be passed."; + } + enum UNKNOWN { + value 4; + description + "Status cannot be determined for some reason."; + } + enum DORMANT { + value 5; + description + "Waiting for some external event."; + } + enum NOT_PRESENT { + value 6; + description + "Some component (typically hardware) is missing."; + } + enum LOWER_LAYER_DOWN { + value 7; + description + "Down due to state of lower-layer interface(s)."; + } + } + //TODO:consider converting to an identity to have the + //flexibility to remove some values defined by RFC 7223 that + //are not used or not implemented consistently. + mandatory true; + description + "The current operational state of the interface. + + This leaf has the same semantics as ifOperStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifOperStatus"; + oc-ext:telemetry-on-change; + } + + leaf last-change { + type oc-types:timeticks64; + units nanoseconds; + description + "This timestamp indicates the absolute time of the last + state change of the interface (e.g., up-to-down transition). + This is different than the SNMP ifLastChange object in the + standard interface MIB in that it is not relative to the + system boot time (i.e,. sysUpTime). + + The value is the timestamp in nanoseconds relative to + the Unix Epoch (Jan 1, 1970 00:00:00 UTC)."; + oc-ext:telemetry-on-change; + } + + leaf logical { + type boolean; + description + "When set to true, the interface is a logical interface + which does not have an associated physical port or + channel on the system."; + oc-ext:telemetry-on-change; + } + } + + + grouping interface-counters-state { + description + "Operational state representing interface counters + and statistics."; + + //TODO: we may need to break this list of counters into those + //that would appear for physical vs. subinterface or logical + //interfaces. For now, just replicating the full stats + //grouping to both interface and subinterface. + + oc-ext:operational; + + container counters { + description + "A collection of interface-related statistics objects."; + + leaf in-octets { + type oc-yang:counter64; + description + "The total number of octets received on the interface, + including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; + } + + leaf in-pkts { + type oc-yang:counter64; + description + "The total number of packets received on the interface, + including all unicast, multicast, broadcast and bad packets + etc."; + reference + "RFC 2819: Remote Network Monitoring Management Information + Base"; + } + + leaf in-unicast-pkts { + type oc-yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were not addressed to a + multicast or broadcast address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; + } + + leaf in-broadcast-pkts { + type oc-yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a broadcast + address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInBroadcastPkts"; + } + + leaf in-multicast-pkts { + type oc-yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a multicast + address at this sub-layer. For a MAC-layer protocol, + this includes both Group and Functional addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInMulticastPkts"; + } + + leaf in-discards { + type oc-yang:counter64; + description + "The number of inbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being deliverable to a higher-layer + protocol. One possible reason for discarding such a + packet could be to free up buffer space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + + + reference + "RFC 2863: The Interfaces Group MIB - ifInDiscards"; + } + + leaf in-errors { + type oc-yang:counter64; + description + "For packet-oriented interfaces, the number of inbound + packets that contained errors preventing them from being + deliverable to a higher-layer protocol. For character- + oriented or fixed-length interfaces, the number of + inbound transmission units that contained errors + preventing them from being deliverable to a higher-layer + protocol. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInErrors"; + } + + leaf in-unknown-protos { + type oc-yang:counter64; + description + "For packet-oriented interfaces, the number of packets + received via the interface that were discarded because + of an unknown or unsupported protocol. For + character-oriented or fixed-length interfaces that + support protocol multiplexing, the number of + transmission units received via the interface that were + discarded because of an unknown or unsupported protocol. + For any interface that does not support protocol + multiplexing, this counter is not present. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; + } + + leaf in-fcs-errors { + type oc-yang:counter64; + description + "Number of received packets which had errors in the + frame check sequence (FCS), i.e., framing errors. + + Discontinuities in the value of this counter can occur + when the device is re-initialization as indicated by the + value of 'last-clear'."; + } + + leaf out-octets { + type oc-yang:counter64; + description + "The total number of octets transmitted out of the + interface, including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; + } + + leaf out-pkts { + type oc-yang:counter64; + description + "The total number of packets transmitted out of the + interface, including all unicast, multicast, broadcast, + and bad packets etc."; + reference + "RFC 2819: Remote Network Monitoring Management Information + Base"; + } + + leaf out-unicast-pkts { + type oc-yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were not addressed + to a multicast or broadcast address at this sub-layer, + including those that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; + } + + leaf out-broadcast-pkts { + type oc-yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were addressed to a + broadcast address at this sub-layer, including those + that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutBroadcastPkts"; + } + + + leaf out-multicast-pkts { + type oc-yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were addressed to a + multicast address at this sub-layer, including those + that were discarded or not sent. For a MAC-layer + protocol, this includes both Group and Functional + addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutMulticastPkts"; + } + + leaf out-discards { + type oc-yang:counter64; + description + "The number of outbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being transmitted. One possible reason + for discarding such a packet could be to free up buffer + space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; + } + + leaf out-errors { + type oc-yang:counter64; + description + "For packet-oriented interfaces, the number of outbound + packets that could not be transmitted because of errors. + For character-oriented or fixed-length interfaces, the + number of outbound transmission units that could not be + transmitted because of errors. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'last-clear'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutErrors"; + } + + leaf carrier-transitions { + type oc-yang:counter64; + description + "Number of times the interface state has transitioned + between up and down since the time the device restarted + or the last-clear time, whichever is most recent."; + oc-ext:telemetry-on-change; + } + + leaf last-clear { + type oc-types:timeticks64; + units nanoseconds; + description + "Timestamp of the last time the interface counters were + cleared. + + The value is the timestamp in nanoseconds relative to + the Unix Epoch (Jan 1, 1970 00:00:00 UTC)."; + oc-ext:telemetry-on-change; + } + } + } + + // data definition statements + + grouping sub-unnumbered-config { + description + "Configuration data for unnumbered subinterfaces"; + + leaf enabled { + type boolean; + default false; + description + "Indicates that the subinterface is unnumbered. By default + the subinterface is numbered, i.e., expected to have an + IP address configuration."; + } + } + + grouping sub-unnumbered-state { + description + "Operational state data unnumbered subinterfaces"; + } + + grouping sub-unnumbered-top { + description + "Top-level grouping unnumbered subinterfaces"; + + container unnumbered { + description + "Top-level container for setting unnumbered interfaces. + Includes reference the interface that provides the + address information"; + + container config { + description + "Configuration data for unnumbered interface"; + oc-ext:telemetry-on-change; + + uses sub-unnumbered-config; + } + + container state { + + config false; + + description + "Operational state data for unnumbered interfaces"; + + uses sub-unnumbered-config; + uses sub-unnumbered-state; + } + + uses oc-if:interface-ref; + } + } + + grouping subinterfaces-config { + description + "Configuration data for subinterfaces"; + + leaf index { + type uint32; + default 0; + description + "The index of the subinterface, or logical interface number. + On systems with no support for subinterfaces, or not using + subinterfaces, this value should default to 0, i.e., the + default subinterface."; + } + + uses interface-common-config; + + } + + grouping subinterfaces-state { + description + "Operational state data for subinterfaces"; + + oc-ext:operational; + + leaf name { + type string; + description + "The system-assigned name for the sub-interface. This MAY + be a combination of the base interface name and the + subinterface index, or some other convention used by the + system."; + oc-ext:telemetry-on-change; + } + + uses interface-common-state; + uses interface-counters-state; + } + + grouping subinterfaces-top { + description + "Subinterface data for logical interfaces associated with a + given interface"; + + container subinterfaces { + description + "Enclosing container for the list of subinterfaces associated + with a physical interface"; + + list subinterface { + key "index"; + + description + "The list of subinterfaces (logical interfaces) associated + with a physical interface"; + + leaf index { + type leafref { + path "../config/index"; + } + description + "The index number of the subinterface -- used to address + the logical interface"; + } + + container config { + description + "Configurable items at the subinterface level"; + oc-ext:telemetry-on-change; + + uses subinterfaces-config; + } + + container state { + + config false; + description + "Operational state data for logical interfaces"; + + uses subinterfaces-config; + uses subinterfaces-state; + } + } + } + } + + grouping interfaces-top { + description + "Top-level grouping for interface configuration and + operational state data"; + + container interfaces { + description + "Top level container for interfaces, including configuration + and state data."; + + + list interface { + key "name"; + + description + "The list of named interfaces on the device."; + + leaf name { + type leafref { + path "../config/name"; + } + description + "References the configured name of the interface"; + //TODO: need to consider whether this should actually + //reference the name in the state subtree, which + //presumably would be the system-assigned name, or the + //configured name. Points to the config/name now + //because of YANG 1.0 limitation that the list + //key must have the same "config" as the list, and + //also can't point to a non-config node. + } + + container config { + description + "Configurable items at the global, physical interface + level"; + oc-ext:telemetry-on-change; + + uses interface-phys-config; + } + + container state { + + config false; + description + "Operational state data at the global interface level"; + + uses interface-phys-config; + uses interface-common-state; + uses interface-counters-state; + } + + uses interface-phys-holdtime-top; + uses subinterfaces-top; + } + } + } + + uses interfaces-top; + +} diff --git a/models/yang/openconfig-lldp.yang b/models/yang/openconfig-lldp.yang new file mode 100644 index 000000000..e687b7c61 --- /dev/null +++ b/models/yang/openconfig-lldp.yang @@ -0,0 +1,660 @@ +module openconfig-lldp { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/lldp"; + + prefix "oc-lldp"; + + import openconfig-lldp-types { prefix oc-lldp-types; } + import openconfig-interfaces { prefix oc-if; } + import ietf-yang-types { prefix yang; } + import openconfig-extensions { prefix oc-ext; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines configuration and operational state data + for the LLDP protocol."; + + oc-ext:openconfig-version "0.2.1"; + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.2.1"; + } + + revision "2018-07-17" { + description + "Adds ttl to lldp-neighbor-state"; + reference "0.2.0"; + } + + revision "2016-05-16" { + description + "Initial public revision"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // identity statements + + + // grouping statements + + grouping lldp-common-counters { + description + "Definition of global and per-interface counters"; + + leaf frame-in { + type yang:counter64; + description + "The number of lldp frames received."; + } + + leaf frame-out { + type yang:counter64; + description + "The number of frames transmitted out."; + } + + leaf frame-error-in { + type yang:counter64; + description + "The number of LLDP frames received with errors."; + } + + leaf frame-discard { + type yang:counter64; + description + "The number of LLDP frames received and discarded."; + } + + leaf tlv-discard { + type yang:counter64; + description + "The number of TLV frames received and discarded."; + } + + leaf tlv-unknown { + type yang:counter64; + description + "The number of frames received with unknown TLV."; + } + + leaf last-clear { + type yang:date-and-time; + description + "Indicates the last time the counters were + cleared."; + } + } + + grouping lldp-global-counters { + description + "Definition of global LLDP counters"; + + uses lldp-common-counters; + + leaf tlv-accepted { + type yang:counter64; + description + "The number of valid TLVs received."; + } + + leaf entries-aged-out { + type yang:counter64; + description + "The number of entries aged out due to timeout."; + } + + } + + grouping lldp-interface-counters { + description + "Definition of per-interface LLDP counters"; + + uses lldp-common-counters; + + leaf frame-error-out { + type yang:counter64; + description + "The number of frame transmit errors on the + interface."; + } + } + + grouping lldp-system-info-config { + description + "Configuration data for system-level local and remote + LLDP information"; + + leaf system-name { + type string { + length 0..255; + } + description + "The system name field shall contain an alpha-numeric string + that indicates the system's administratively assigned name. + The system name should be the system's fully qualified domain + name. If implementations support IETF RFC 3418, the sysName + object should be used for this field."; + } + + leaf system-description { + type string { + length 0..255; + } + description + "The system description field shall contain an alpha-numeric + string that is the textual description of the network entity. + The system description should include the full name and + version identification of the system's hardware type, + software operating system, and networking software. If + implementations support IETF RFC 3418, the sysDescr object + should be used for this field."; + } + + leaf chassis-id { + type string; + description + "The Chassis ID is a mandatory TLV which identifies the + chassis component of the endpoint identifier associated with + the transmitting LLDP agent"; + } + + leaf chassis-id-type { + type oc-lldp-types:chassis-id-type; + description + "This field identifies the format and source of the chassis + identifier string. It is an enumerator defined by the + LldpChassisIdSubtype object from IEEE 802.1AB MIB."; + } + } + + grouping lldp-system-info-state { + description + "Operational state data reported for the local and remote + systems"; + + } + + grouping lldp-neighbor-config { + description + "Configuration data for LLDP neighbors"; + + } + + grouping lldp-neighbor-state { + description + "Operational state data for LLDP neighbors"; + + leaf id { + type string; + description + "System generated identifier for the neighbor on the + interface."; + } + + leaf age { + type uint64; + units "seconds"; + description + "Age since discovery"; + } + + leaf last-update { + type int64; + description + "Seconds since last update received."; + } + + leaf ttl { + type uint16; + units "seconds"; + description + "The time-to-live (TTL) is a mandatory TLV which indicates + how long information from the neighbor should be considered + valid."; + } + + leaf port-id { + type string; + description + "The Port ID is a mandatory TLV which identifies the port + component of the endpoint identifier associated with the + transmitting LLDP agent. If the specified port is an IEEE + 802.3 Repeater port, then this TLV is optional."; + } + + leaf port-id-type { + type oc-lldp-types:port-id-type; + description + "This field identifies the format and source of the port + identifier string. It is an enumerator defined by the + PtopoPortIdType object from RFC2922."; + } + + leaf port-description { + type string; + description + "The binary string containing the actual port identifier for + the port which this LLDP PDU was transmitted. The source and + format of this field is defined by PtopoPortId from + RFC2922."; + } + + leaf management-address { + type string; + description + "The Management Address is a mandatory TLV which identifies a + network address associated with the local LLDP agent, which + can be used to reach the agent on the port identified in the + Port ID TLV."; + } + + leaf management-address-type { + type string; + description + "The enumerated value for the network address type + identified in this TLV. This enumeration is defined in the + 'Assigned Numbers' RFC [RFC3232] and the + ianaAddressFamilyNumbers object."; + } + } + + grouping lldp-capabilities-config { + description + "Configuration data for LLDP capabilities"; + } + + grouping lldp-capabilities-state { + description + "Operational state data for LLDP capabilities"; + + leaf name { + type identityref { + base oc-lldp-types:LLDP_SYSTEM_CAPABILITY; + } + description + "Name of the system capability advertised by the neighbor. + Capabilities are represented in a bitmap that defines the + primary functions of the system. The capabilities are + defined in IEEE 802.1AB."; + } + + leaf enabled { + type boolean; + description + "Indicates whether the corresponding system capability is + enabled on the neighbor."; + reference + "Sec 8.5.8.2 of IEEE 802.1AB-2009"; + } + } + + grouping lldp-capabilities-top { + description + "Top-level grouping for LLDP capabilities"; + + container capabilities { + config false; + description + "Enclosing container for list of LLDP capabilities"; + + list capability { + key "name"; + description + "List of LLDP system capabilities advertised by the + neighbor"; + + leaf name { + type leafref { + path "../state/name"; + } + description + "Reference to capabilities list key"; + } + + container config { + description + "Configuration data for LLDP capabilities"; + + uses lldp-capabilities-config; + } + + container state { + + config false; + + description + "Operational state data for LLDP capabilities"; + + uses lldp-capabilities-config; + uses lldp-capabilities-state; + } + } + } + } + + grouping lldp-custom-tlv-config { + description + "Configuration data for custom LLDP TLVs"; + } + + grouping lldp-custom-tlv-state { + description + "Operational state data for custom LLDP TLVs"; + + leaf type { + type int32; + description + "The integer value identifying the type of information + contained in the value field."; + } + + leaf oui { + type string; + description + "The organizationally unique identifier field shall contain + the organization's OUI as defined in Clause 9 of IEEE Std + 802. The high-order octet is 0 and the low-order 3 octets + are the SMI Network Management Private Enterprise Code of + the Vendor in network byte order, as defined in the + 'Assigned Numbers' RFC [RFC3232]."; + } + + leaf oui-subtype { + type string; + description + "The organizationally defined subtype field shall contain a + unique subtype value assigned by the defining organization."; + } + + // TODO: consider making this string type + leaf value { + type binary; + description + "A variable-length octet-string containing the + instance-specific information for this TLV."; + } + } + + grouping lldp-custom-tlv-top { + description + "Top-level grouping for custom LLDP TLVs"; + + container custom-tlvs { + config false; + description + "Enclosing container for list of custom TLVs from a + neighbor"; + + list tlv { + key "type oui oui-subtype"; + description + "List of custom LLDP TLVs from a neighbor"; + + leaf type { + type leafref { + path "../state/type"; + } + description + "Reference to type list key"; + } + + leaf oui { + type leafref { + path "../state/oui"; + } + description + "Reference to oui list key"; + } + + leaf oui-subtype { + type leafref { + path "../state/oui-subtype"; + } + description + "Reference to oui-subtype list key"; + } + + container config { + description + "Configuration data "; + + uses lldp-custom-tlv-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses lldp-custom-tlv-config; + uses lldp-custom-tlv-state; + } + } + } + } + + grouping lldp-neighbor-top { + description + "Top-level grouping for the LLDP neighbor list"; + + container neighbors { + config false; + description + "Enclosing container for list of LLDP neighbors on an + interface"; + + list neighbor { + key "id"; + description + "List of LLDP neighbors"; + + leaf id { + type leafref { + path "../state/id"; + } + description + " "; + } + + container config { + description + "Configuration data "; + + uses lldp-neighbor-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses lldp-system-info-config; + uses lldp-system-info-state; + uses lldp-neighbor-config; + uses lldp-neighbor-state; + } + + uses lldp-custom-tlv-top; + uses lldp-capabilities-top; + } + } + } + + grouping lldp-interface-config { + description + "Configuration data for LLDP on each interface"; + + leaf name { + type oc-if:base-interface-ref; + description + "Reference to the LLDP Ethernet interface"; + } + + leaf enabled { + type boolean; + default "true"; + description + "Enable or disable the LLDP protocol on the interface."; + } + } + + grouping lldp-interface-state { + description + "Operational state data for LLDP on each interface"; + + container counters { + description + "LLDP counters on each interface"; + + uses lldp-interface-counters; + } + } + + grouping lldp-interface-top { + description + "Top-level grouping "; + + container interfaces { + description + "Enclosing container "; + + list interface { + key "name"; + description + "List of interfaces on which LLDP is enabled / available"; + + leaf name { + type leafref { + path "../config/name"; + } + description + "Reference to the list key"; + } + + container config { + description + "Configuration data for LLDP on each interface"; + + uses lldp-interface-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses lldp-interface-config; + uses lldp-interface-state; + } + + uses lldp-neighbor-top; + } + } + } + + + grouping lldp-config { + description + "Configuration data for global LLDP parameters"; + + leaf enabled { + type boolean; + default "true"; + description + "System level state of the LLDP protocol."; + } + + leaf hello-timer { + type uint64; + units "seconds"; + description + "System level hello timer for the LLDP protocol."; + } + + leaf-list suppress-tlv-advertisement { + type identityref { + base oc-lldp-types:LLDP_TLV; + } + description + "Indicates whether the local system should suppress the + advertisement of particular TLVs with the LLDP PDUs that it + transmits. Where a TLV type is specified within this list, it + should not be included in any LLDP PDU transmitted by the + local agent."; + } + } + + grouping lldp-state { + description + "Operational state data for global LLDP parameters"; + + container counters { + description + "Global LLDP counters"; + + uses lldp-global-counters; + } + } + + grouping lldp-top { + description + "Top-level grouping for LLDP model"; + + container lldp { + description + "Top-level container for LLDP configuration and state data"; + + container config { + description + "Configuration data "; + + uses lldp-config; + uses lldp-system-info-config; + } + + container state { + + config false; + + description + "Operational state data "; + + uses lldp-config; + uses lldp-system-info-config; + uses lldp-system-info-state; + uses lldp-state; + } + + uses lldp-interface-top; + } + } + + // data definition statements + + uses lldp-top; + + +} diff --git a/models/yang/openconfig-platform.yang b/models/yang/openconfig-platform.yang new file mode 100644 index 000000000..ecf38cd1a --- /dev/null +++ b/models/yang/openconfig-platform.yang @@ -0,0 +1,779 @@ +module openconfig-platform { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/platform"; + + prefix "oc-platform"; + + import openconfig-platform-types { prefix oc-platform-types; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-alarm-types { prefix oc-alarm-types; } + import openconfig-yang-types { prefix oc-yang; } + + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + www.openconfig.net"; + + description + "This module defines a data model for representing a system + component inventory, which can include hardware or software + elements arranged in an arbitrary structure. The primary + relationship supported by the model is containment, e.g., + components containing subcomponents. + + It is expected that this model reflects every field replacable + unit on the device at a minimum (i.e., additional information + may be supplied about non-replacable components). + + Every element in the inventory is termed a 'component' with each + component expected to have a unique name and type, and optionally + a unique system-assigned identifier and FRU number. The + uniqueness is guaranteed by the system within the device. + + Components may have properties defined by the system that are + modeled as a list of key-value pairs. These may or may not be + user-configurable. The model provides a flag for the system + to optionally indicate which properties are user configurable. + + Each component also has a list of 'subcomponents' which are + references to other components. Appearance in a list of + subcomponents indicates a containment relationship as described + above. For example, a linecard component may have a list of + references to port components that reside on the linecard. + + This schema is generic to allow devices to express their own + platform-specific structure. It may be augmented by additional + component type-specific schemas that provide a common structure + for well-known component types. In these cases, the system is + expected to populate the common component schema, and may + optionally also represent the component and its properties in the + generic structure. + + The properties for each component may include dynamic values, + e.g., in the 'state' part of the schema. For example, a CPU + component may report its utilization, temperature, or other + physical properties. The intent is to capture all platform- + specific physical data in one location, including inventory + (presence or absence of a component) and state (physical + attributes or status)."; + + oc-ext:openconfig-version "0.12.2"; + + revision "2019-04-16" { + description + "Fix bug in parent path reference"; + reference "0.12.2"; + } + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.12.1"; + } + + revision "2018-06-29" { + description + "Added location description for components"; + reference "0.12.0"; + } + + revision "2018-06-03" { + description + "Added parent reference, empty flag and preconfiguration + for components"; + reference "0.11.0"; + } + + revision "2018-04-20" { + description + "Added new per-component state data: mfg-date and removable"; + reference "0.10.0"; + } + + revision "2018-01-30" { + description + "Amended approach for modelling CPU - rather than having + a local CPU utilisation state variable, a component with + a CPU should create a subcomponent of type CPU to report + statistics."; + reference "0.9.0"; + } + + revision "2018-01-16" { + description + "Added new per-component common data; add temp alarm; + moved hardware-port reference to port model"; + reference "0.8.0"; + } + + revision "2017-12-14" { + description + "Added anchor containers for component data, added new + component types"; + reference "0.7.0"; + } + + revision "2017-08-16" { + description + "Added power state enumerated type"; + reference "0.6.0"; + } + + revision "2016-12-22" { + description + "Added temperature state variable to component"; + reference "0.5.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // grouping statements + + + grouping platform-component-properties-config { + description + "System-defined configuration data for component properties"; + + leaf name { + type string; + description + "System-supplied name of the property -- this is typically + non-configurable"; + } + + leaf value { + type union { + type string; + type boolean; + type int64; + type uint64; + type decimal64 { + fraction-digits 2; + } + } + description + "Property values can take on a variety of types. Signed and + unsigned integer types may be provided in smaller sizes, + e.g., int8, uint16, etc."; + } + } + + grouping platform-component-properties-state { + description + "Operational state data for component properties"; + + leaf configurable { + type boolean; + description + "Indication whether the property is user-configurable"; + } + } + + grouping platform-component-properties-top { + description + "Top-level grouping "; + + container properties { + description + "Enclosing container "; + + list property { + key "name"; + description + "List of system properties for the component"; + + leaf name { + type leafref { + path "../config/name"; + } + description + "Reference to the property name."; + } + + container config { + description + "Configuration data for each property"; + + uses platform-component-properties-config; + } + + container state { + + config false; + + description + "Operational state data for each property"; + + uses platform-component-properties-config; + uses platform-component-properties-state; + } + } + } + } + + grouping platform-subcomponent-ref-config { + description + "Configuration data for subcomponent references"; + + leaf name { + type leafref { + path "../../../../../component/config/name"; + } + description + "Reference to the name of the subcomponent"; + } + } + + grouping platform-subcomponent-ref-state { + description + "Operational state data for subcomponent references"; + + } + + grouping platform-subcomponent-ref-top { + description + "Top-level grouping for list of subcomponent references"; + + container subcomponents { + description + "Enclosing container for subcomponent references"; + + list subcomponent { + key "name"; + description + "List of subcomponent references"; + + leaf name { + type leafref { + path "../config/name"; + } + description + "Reference to the name list key"; + } + + container config { + description + "Configuration data for the subcomponent"; + + uses platform-subcomponent-ref-config; + } + + container state { + + config false; + + description + "Operational state data for the subcomponent"; + + uses platform-subcomponent-ref-config; + uses platform-subcomponent-ref-state; + } + } + } + } + + grouping platform-component-config { + description + "Configuration data for components"; + + leaf name { + type string; + description + "Device name for the component -- this may not be a + configurable parameter on many implementations. Where + component preconfiguration is supported, for example, + the component name may be configurable."; + } + } + + grouping platform-component-state { + description + "Operational state data for device components."; + + leaf type { + type union { + type identityref { + base oc-platform-types:OPENCONFIG_HARDWARE_COMPONENT; + } + type identityref { + base oc-platform-types:OPENCONFIG_SOFTWARE_COMPONENT; + } + } + description + "Type of component as identified by the system"; + } + + leaf id { + type string; + description + "Unique identifier assigned by the system for the + component"; + } + + leaf location { + type string; + description + "System-supplied description of the location of the + component within the system. This could be a bay position, + slot number, socket location, etc. For component types that + have an explicit slot-id attribute, such as linecards, the + system should populate the more specific slot-id."; + } + + leaf description { + type string; + description + "System-supplied description of the component"; + } + + leaf mfg-name { + type string; + description + "System-supplied identifier for the manufacturer of the + component. This data is particularly useful when a + component manufacturer is different than the overall + device vendor."; + } + + leaf mfg-date { + type oc-yang:date; + description + "System-supplied representation of the component's + manufacturing date."; + } + + leaf hardware-version { + type string; + description + "For hardware components, this is the hardware revision of + the component."; + } + + leaf firmware-version { + type string; + description + "For hardware components, this is the version of associated + firmware that is running on the component, if applicable."; + } + + leaf software-version { + type string; + description + "For software components such as operating system or other + software module, this is the version of the currently + running software."; + } + + leaf serial-no { + type string; + description + "System-assigned serial number of the component."; + } + + leaf part-no { + type string; + description + "System-assigned part number for the component. This should + be present in particular if the component is also an FRU + (field replaceable unit)"; + } + + leaf removable { + type boolean; + description + "If true, this component is removable or is a field + replaceable unit"; + } + + leaf oper-status { + type identityref { + base oc-platform-types:COMPONENT_OPER_STATUS; + } + description + "If applicable, this reports the current operational status + of the component."; + } + + leaf empty { + type boolean; + default false; + description + "The empty leaf may be used by the device to indicate that a + component position exists but is not populated. Using this + flag, it is possible for the management system to learn how + many positions are available (e.g., occupied vs. empty + linecard slots in a chassis)."; + } + + leaf parent { + type leafref { + path "../../../component/config/name"; + } + description + "Reference to the name of the parent component. Note that + this reference must be kept synchronized with the + corresponding subcomponent reference from the parent + component."; + } + } + + grouping platform-component-temp-alarm-state { + description + "Temperature alarm data for platform components"; + + // TODO(aashaikh): consider if these leaves could be in a + // reusable grouping (not temperature-specific); threshold + // may always need to be units specific. + + leaf alarm-status { + type boolean; + description + "A value of true indicates the alarm has been raised or + asserted. The value should be false when the alarm is + cleared."; + } + + leaf alarm-threshold { + type uint32; + description + "The threshold value that was crossed for this alarm."; + } + + leaf alarm-severity { + type identityref { + base oc-alarm-types:OPENCONFIG_ALARM_SEVERITY; + } + description + "The severity of the current alarm."; + } + } + + grouping platform-component-power-state { + description + "Power-related operational state for device components."; + + leaf allocated-power { + type uint32; + units watts; + description + "Power allocated by the system for the component."; + } + + leaf used-power { + type uint32; + units watts; + description + "Actual power used by the component."; + } + } + + grouping platform-component-temp-state { + description + "Temperature state data for device components"; + + container temperature { + description + "Temperature in degrees Celsius of the component. Values include + the instantaneous, average, minimum, and maximum statistics. If + avg/min/max statistics are not supported, the target is expected + to just supply the instant value"; + + uses oc-platform-types:avg-min-max-instant-stats-precision1-celsius; + uses platform-component-temp-alarm-state; + } + } + + grouping platform-component-memory-state { + description + "Per-component memory statistics"; + + container memory { + description + "For components that have associated memory, these values + report information about available and utilized memory."; + + leaf available { + type uint64; + units bytes; + description + "The available memory physically installed, or logically + allocated to the component."; + } + + // TODO(aashaikh): consider if this needs to be a + // min/max/avg statistic + leaf utilized { + type uint64; + units bytes; + description + "The memory currently in use by processes running on + the component, not considering reserved memory that is + not available for use."; + } + } + } + + grouping platform-anchors-top { + description + "This grouping is used to add containers for components that + are common across systems, but do not have a defined schema + within the openconfig-platform module. Containers should be + added to this grouping for components that are expected to + exist in multiple systems, with corresponding modules + augmenting the config/state containers directly."; + + container chassis { + description + "Data for chassis components"; + + container config { + description + "Configuration data for chassis components"; + } + + container state { + config false; + description + "Operational state data for chassis components"; + } + } + +// TODO(aashaikh): linecard container is already defined in +// openconfig-platform-linecard; will move to this module +// in future. + /* + container linecard { + description + "Data for linecard components"; + + container config { + description + "Configuration data for linecard components"; + } + + container state { + config false; + description + "Operational state data for linecard components"; + } + } + */ + + container port { + description + "Data for physical port components"; + + container config { + description + "Configuration data for physical port components"; + } + + container state { + config false; + description + "Operational state data for physical port components"; + } + } + +// TODO(aashaikh): transceiver container is already defined in +// openconfig-platform-transceiver; will move to this module +// in future. + /* + container transceiver { + description + "Data for transceiver components"; + + container config { + description + "Configuration data for transceiver components"; + } + + container state { + config false; + description + "Operational state data for transceiver components"; + } + } + */ + + container power-supply { + description + "Data for power supply components"; + + container config { + description + "Configuration data for power supply components"; + } + + container state { + config false; + description + "Operational state data for power supply components"; + } + } + + container fan { + description + "Data for fan components"; + + container config { + description + "Configuration data for fan components"; + } + + container state { + config false; + description + "Operational state data for fan components"; + } + } + + container fabric { + description + "Data for fabric components"; + + container config { + description + "Configuration data for fabric components"; + } + + container state { + config false; + description + "Operational state data for fabric components"; + } + } + + container storage { + description + "Data for storage components"; + + container config { + description + "Configuration data for storage components"; + } + + container state { + config false; + description + "Operational state data for storage components"; + } + } + + container cpu { + description + "Data for cpu components"; + + container config { + description + "Configuration data for cpu components"; + } + + container state { + config false; + description + "Operational state data for cpu components"; + } + } + + container integrated-circuit { + description + "Data for chip components, such as ASIC, NPUs, etc."; + + container config { + description + "Configuration data for chip components"; + } + + container state { + config false; + description + "Operational state data for chip components"; + } + } + + container backplane { + description + "Data for backplane components"; + + container config { + description + "Configuration data for backplane components"; + } + + container state { + config false; + description + "Operational state data for backplane components"; + } + } + } + + grouping platform-component-top { + description + "Top-level grouping for components in the device inventory"; + + container components { + description + "Enclosing container for the components in the system."; + + list component { + key "name"; + description + "List of components, keyed by component name."; + + leaf name { + type leafref { + path "../config/name"; + } + description + "References the component name"; + } + + container config { + description + "Configuration data for each component"; + + uses platform-component-config; + } + + container state { + + config false; + + description + "Operational state data for each component"; + + uses platform-component-config; + uses platform-component-state; + uses platform-component-temp-state; + uses platform-component-memory-state; + uses platform-component-power-state; + } + + uses platform-component-properties-top; + uses platform-subcomponent-ref-top; + uses platform-anchors-top; + } + } + } + + + // data definition statements + + uses platform-component-top; + + + // augments + + +} diff --git a/models/yang/openconfig-system.yang b/models/yang/openconfig-system.yang new file mode 100644 index 000000000..fd05a3f77 --- /dev/null +++ b/models/yang/openconfig-system.yang @@ -0,0 +1,997 @@ +module openconfig-system { + + yang-version "1"; + + // namespace + namespace "http://openconfig.net/yang/system"; + + prefix "oc-sys"; + + // import some basic types + import openconfig-inet-types { prefix oc-inet; } + import openconfig-yang-types { prefix oc-yang; } + import openconfig-types { prefix oc-types; } + import openconfig-extensions { prefix oc-ext; } + import openconfig-aaa { prefix oc-aaa; } + import openconfig-system-logging { prefix oc-log; } + import openconfig-system-management { prefix oc-sys-mgmt; } + import openconfig-system-terminal { prefix oc-sys-term; } + import openconfig-procmon { prefix oc-proc; } + import openconfig-alarms { prefix oc-alarms; } + import openconfig-messages { prefix oc-messages; } + + // meta + organization "OpenConfig working group"; + + contact + "OpenConfig working group + netopenconfig@googlegroups.com"; + + description + "Model for managing system-wide services and functions on + network devices. + + Portions of this code were derived from IETF RFC 7317. + Please reproduce this note if possible. + + IETF code is subject to the following copyright and license: + Copyright (c) IETF Trust and the persons identified as authors of + the code. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in + Section 4.c of the IETF Trust's Legal Provisions Relating + to IETF Documents (http://trustee.ietf.org/license-info)."; + + oc-ext:openconfig-version "0.7.0"; + + revision "2019-01-29" { + description + "Add messages module to the system model"; + reference "0.7.0"; + } + + revision "2018-11-21" { + description + "Add OpenConfig module metadata extensions."; + reference "0.6.1"; + } + + revision "2018-07-17" { + description + "Add gRPC server data"; + reference "0.6.0"; + } + + revision "2018-01-21" { + description + "Add cpu utilization data"; + reference "0.5.0"; + } + + revision "2017-12-15" { + description + "Add alarms to the system model"; + reference "0.4.0"; + } + + revision "2017-09-18" { + description + "Updated to use OpenConfig types modules"; + reference "0.3.0"; + } + + revision "2017-07-06" { + description + "Move to oc-inet types, add IETF attribution, add RADIUS + counters, changed password leaf names to indicate hashed"; + reference "0.2.0"; + } + + revision "2017-01-29" { + description + "Initial public release"; + reference "0.1.0"; + } + + // OpenConfig specific extensions for module metadata. + oc-ext:regexp-posix; + oc-ext:catalog-organization "openconfig"; + oc-ext:origin "openconfig"; + + // extension statements + + // feature statements + + // identity statements + + identity NTP_AUTH_TYPE { + description + "Base identity for encryption schemes supported for NTP + authentication keys"; + } + + identity NTP_AUTH_MD5 { + base NTP_AUTH_TYPE; + description + "MD5 encryption method"; + } + + // typedef statements + + typedef timezone-name-type { + type string; + description + "A time zone name as used by the Time Zone Database, + sometimes referred to as the 'Olson Database'. + + The exact set of valid values is an implementation-specific + matter. Client discovery of the exact set of time zone names + for a particular server is out of scope."; + reference + "BCP 175: Procedures for Maintaining the Time Zone Database"; + } + + // grouping statements + + grouping system-clock-config { + description + "Configuration data for system-wide clock configuration"; + + leaf timezone-name { + type timezone-name-type; + description + "The TZ database name to use for the system, such + as 'Europe/Stockholm'."; + reference "IANA Time Zone Database + http://www.iana.org/time-zones"; + } + } + + grouping system-clock-state { + description + "Operational state data for system-wide clock configuration"; + } + + grouping system-clock-top { + description + "Top-level grouping for system-wide clock configuration"; + + container clock { + description + "Top-level container for clock configuration data"; + + container config { + description + "Configuration data for system clock"; + + uses system-clock-config; + } + + container state { + + config false; + + description + "Operational state data for system clock"; + + uses system-clock-config; + uses system-clock-state; + } + } + } + + grouping system-global-config { + description "system-wide configuration parameters"; + + leaf hostname { + type oc-inet:domain-name; + description + "The hostname of the device -- should be a single domain + label, without the domain."; + } + + leaf domain-name { + type oc-inet:domain-name; + description + "Specifies the domain name used to form fully qualified name + for unqualified hostnames."; + } + + leaf login-banner { + type string; + description + "The console login message displayed before the login prompt, + i.e., before a user logs into the system."; + } + + leaf motd-banner { + type string; + description + "The console message displayed after a user logs into the + system. They system may append additional standard + information such as the current system date and time, uptime, + last login timestamp, etc."; + } + } + + grouping system-global-state { + description + "Global operational state data for the system"; + + leaf current-datetime { + type oc-yang:date-and-time; + description + "The current system date and time."; + } + + leaf boot-time { + type oc-types:timeticks64; + description + "This timestamp indicates the time that the system was last + restarted. The value is the timestamp in seconds relative + to the Unix Epoch (Jan 1, 1970 00:00:00 UTC)."; + } + + } + + grouping system-dns-config { + description "DNS / resolver related configuration data"; + + leaf-list search { + type oc-inet:domain-name; + ordered-by user; + description + "An ordered list of domains to search when resolving + a host name."; + } + } + + grouping system-dns-state { + description + "Operational state data for system DNS resolver"; + + } + + grouping system-dns-servers-config { + description + "Configuration data for DNS resolvers"; + + //RFC 7317 includes a single-value choice statement to for + //TCP and UDP transport. This has been removed since it the + //transport protocol is not generally available as an options + //on target devices. It may be added back if and when needed. + + leaf address { + type oc-inet:ip-address; + description + "The address of the DNS server, can be either IPv4 + or IPv6."; + } + + leaf port { + type oc-inet:port-number; + default 53; + description + "The port number of the DNS server."; + } + + //RFC 7317 includes resolver timeout and attempts options. These + //have been omitted as they are not available on many targets. If + //and when they are required, they may be added back in. + } + + grouping system-dns-static-config { + description + "Configuration data for static host entries"; + + leaf hostname { + type string; + description + "Hostname for the static DNS entry"; + } + + leaf-list alias { + type string; + description + "Additional aliases for the hostname"; + } + + leaf-list ipv4-address { + type oc-inet:ipv4-address; + description + "List of IPv4 addressses for the host entry"; + } + + leaf-list ipv6-address { + type oc-inet:ipv6-address; + description + "List of IPv6 addresses for the host entry"; + } + } + + grouping system-dns-static-state { + description + "Operational state data for static host entries"; + } + + grouping system-dns-static-top { + description + "Top-level grouping for static DNS host entries"; + + container host-entries { + description + "Enclosing container for list of static host entries"; + + list host-entry { + key "hostname"; + description + "List of static host entries"; + + leaf hostname { + type leafref { + path "../config/hostname"; + } + description + "Reference to the hostname list key"; + } + + container config { + description + "Configuration data for static host entries"; + + uses system-dns-static-config; + } + + container state { + + config false; + + description + "Operational state data for static host entries"; + + uses system-dns-static-config; + uses system-dns-static-state; + } + } + } + } + + grouping system-dns-servers-state { + description + "Operational state data for DNS resolvers"; + + } + + grouping system-dns-servers-top { + description + "Top-level grouping for the list of DNS resolvers."; + + container servers { + description + "Enclosing container for DNS resolver list"; + + list server { + key "address"; + ordered-by user; + description + "List of the DNS servers that the resolver should query. + + When the resolver is invoked by a calling application, it + sends the query to the first name server in this list. If + no response has been received within 'timeout' seconds, + the resolver continues with the next server in the list. + If no response is received from any server, the resolver + continues with the first server again. When the resolver + has traversed the list 'attempts' times without receiving + any response, it gives up and returns an error to the + calling application. + + Implementations MAY limit the number of entries in this + list."; + + leaf address { + type leafref { + path "../config/address"; + } + description + "References the configured address of the DNS server"; + } + + container config { + description + "Configuration data for each DNS resolver"; + + uses system-dns-servers-config; + } + + container state { + + config false; + + description + "Operational state data for each DNS resolver"; + + uses system-dns-servers-config; + uses system-dns-servers-state; + } + + } + } + } + + grouping system-dns-top { + description + "Top-level grouping for DNS / resolver config and operational + state data"; + + container dns { + description + "Enclosing container for DNS resolver data"; + + container config { + description + "Configuration data for the DNS resolver"; + + uses system-dns-config; + + } + + container state { + + config false; + + description + "Operational state data for the DNS resolver"; + + uses system-dns-config; + uses system-dns-state; + + } + + uses system-dns-servers-top; + uses system-dns-static-top; + } + } + + grouping system-ntp-server-config { + description + "Configuration data for NTP servers"; + + leaf address { + type oc-inet:host; + description + "The address or hostname of the NTP server."; + } + + leaf port { + type oc-inet:port-number; + default 123; + description + "The port number of the NTP server."; + } + + leaf version { + type uint8 { + range 1..4; + } + default 4; + description + "Version number to put in outgoing NTP packets"; + } + + leaf association-type { + type enumeration { + enum SERVER { + description + "Use client association mode. This device + will not provide synchronization to the + configured NTP server."; + } + enum PEER { + description + "Use symmetric active association mode. + This device may provide synchronization + to the configured NTP server."; + } + enum POOL { + description + "Use client association mode with one or + more of the NTP servers found by DNS + resolution of the domain name given by + the 'address' leaf. This device will not + provide synchronization to the servers."; + } + } + default SERVER; + description + "The desired association type for this NTP server."; + } + leaf iburst { + type boolean; + default false; + description + "Indicates whether this server should enable burst + synchronization or not."; + } + leaf prefer { + type boolean; + default false; + description + "Indicates whether this server should be preferred + or not."; + } + } + + grouping system-ntp-server-state { + description + "Operational state data for NTP servers"; + + leaf stratum { + type uint8; + description + "Indicates the level of the server in the NTP hierarchy. As + stratum number increases, the accuracy is degraded. Primary + servers are stratum while a maximum value of 16 indicates + unsynchronized. The values have the following specific + semantics: + + | 0 | unspecified or invalid + | 1 | primary server (e.g., equipped with a GPS receiver) + | 2-15 | secondary server (via NTP) + | 16 | unsynchronized + | 17-255 | reserved"; + reference + "RFC 5905 - Network Time Protocol Version 4: Protocol and + Algorithms Specification"; + } + + leaf root-delay { + type uint32; + // TODO: reconsider units for these values -- the spec defines + // rootdelay and rootdisperson as 2 16-bit integers for seconds + // and fractional seconds, respectively. This gives a + // precision of ~15 us (2^-16). Using milliseconds here based + // on what implementations typically provide and likely lack + // of utility for less than millisecond precision with NTP + // time sync. + units "milliseconds"; + description + "The round-trip delay to the server, in milliseconds."; + reference + "RFC 5905 - Network Time Protocol Version 4: Protocol and + Algorithms Specification"; + } + + leaf root-dispersion { + type uint64; + units "milliseconds"; + description + "Dispersion (epsilon) represents the maximum error inherent + in the measurement"; + reference + "RFC 5905 - Network Time Protocol Version 4: Protocol and + Algorithms Specification"; + } + + leaf offset { + type uint64; + units "milliseconds"; + description + "Estimate of the current time offset from the peer. This is + the time difference between the local and reference clock."; + } + + leaf poll-interval { + type uint32; + units "seconds"; + description + "Polling interval of the peer"; + } + } + + grouping system-ntp-server-top { + description + "Top-level grouping for the list of NTP servers"; + + container servers { + description + "Enclosing container for the list of NTP servers"; + + list server { + key "address"; + description + "List of NTP servers to use for system clock + synchronization. If '/system/ntp/enabled' + is 'true', then the system will attempt to + contact and utilize the specified NTP servers."; + + leaf address { + type leafref { + path "../config/address"; + } + description + "References the configured address or hostname of the + NTP server."; + } + + container config { + description + "Configuration data for an NTP server."; + + uses system-ntp-server-config; + } + + container state { + + config false; + + description + "Operational state data for an NTP server."; + + uses system-ntp-server-config; + uses system-ntp-server-state; + } + + } + } + } + + grouping system-ntp-auth-keys-config { + description + "Configuration data "; + + leaf key-id { + type uint16; + description + "Integer identifier used by the client and server to + designate a secret key. The client and server must use + the same key id."; + } + + leaf key-type { + type identityref { + base NTP_AUTH_TYPE; + } + description + "Encryption type used for the NTP authentication key"; + } + + leaf key-value { + type string; + description + "NTP authentication key value"; + } + } + + grouping system-ntp-auth-keys-state { + description + "Operational state data for NTP auth key data"; + } + + grouping system-ntp-auth-keys-top { + description + "Top-level grouping for NTP auth key data"; + + container ntp-keys { + description + "Enclosing container for list of NTP authentication keys"; + + list ntp-key { + key "key-id"; + description + "List of NTP authentication keys"; + + leaf key-id { + type leafref { + path "../config/key-id"; + } + description + "Reference to auth key-id list key"; + } + + container config { + description + "Configuration data for NTP auth keys"; + + uses system-ntp-auth-keys-config; + } + + container state { + + config false; + + description + "Operational state data for NTP auth keys"; + + uses system-ntp-auth-keys-config; + uses system-ntp-auth-keys-state; + } + } + } + } + + grouping system-ntp-config { + description + "Configuration data for system-wide NTP operation."; + + leaf enabled { + type boolean; + default false; + description + "Enables the NTP protocol and indicates that the system should + attempt to synchronize the system clock with an NTP server + from the servers defined in the 'ntp/server' list."; + } + + leaf ntp-source-address { + type oc-inet:ip-address; + description + "Source address to use on outgoing NTP packets"; + } + + leaf enable-ntp-auth { + type boolean; + default false; + description + "Enable or disable NTP authentication -- when enabled, the + system will only use packets containing a trusted + authentication key to synchronize the time."; + } + } + + grouping system-ntp-state { + description + "Operational state data for system-wide NTP operation."; + + leaf auth-mismatch { + type oc-yang:counter64; + description + "Count of the number of NTP packets received that were not + processed due to authentication mismatch."; + } + } + + grouping system-ntp-top { + description + "Top-level grouping for configuration and state data for NTP"; + + container ntp { + description + "Top-level container for NTP configuration and state"; + + container config { + description + "Configuration data for NTP client."; + + uses system-ntp-config; + } + + container state { + config false; + description + "Operational state data for NTP services."; + + uses system-ntp-config; + uses system-ntp-state; + } + uses system-ntp-auth-keys-top; + uses system-ntp-server-top; + } + } + + grouping system-memory-config { + description + "Configuration data for system memory"; + } + + grouping system-memory-state { + description + "Operational state data for system memory"; + + leaf physical { + type uint64; + units bytes; + // TODO: consider making units in megabytes + description + "Reports the total physical memory available on the + system."; + } + + leaf reserved { + type uint64; + units bytes; + description + "Memory reserved for system use"; + } + } + + grouping system-memory-top { + description + "Top-level grouping for system memory data definitions"; + + container memory { + description + "Top-level container for system memory data"; + + container config { + description + "Configuration data for system memory"; + + uses system-memory-config; + } + + container state { + config false; + description + "Operational state data for system memory"; + + uses system-memory-config; + uses system-memory-state; + } + } + } + + grouping system-cpu-state { + description + "Operational state data for the system CPU(s)"; + + leaf index { + type union { + type enumeration { + enum ALL { + description + "Index value indicating all CPUs in the system"; + } + } + type uint32; + } + description + "The CPU index for each processor core on the system. On a + single-core system, the index should be zero. The ALL + index signifies an aggregation of the CPU utilization + statistics over all cores in the system."; + } + + container total { + description + "Total CPU utilization."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container user { + description + "Percentage of CPU time spent running in user space."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container kernel { + description + "Percentage of CPU time spent running in kernel space."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container nice { + description + "Percentage of CPU time spent running low-priority (niced) + user processes."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container idle { + description + "Percentage of CPU time spent idle."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container wait { + description + "Percentage of CPU time spent waiting for I/O."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container hardware-interrupt { + description + "Percentage of CPU time spent servicing hardware interrupts."; + + uses oc-types:avg-min-max-instant-stats-pct; + } + + container software-interrupt { + description + "Percentage of CPU time spent servicing software interrupts"; + + uses oc-types:avg-min-max-instant-stats-pct; + } + } + + grouping system-cpu-top { + description + "Top-level grouping for system CPU data"; + + container cpus { + config false; + description + "Enclosing container for the list of CPU cores on the + system"; + + list cpu { + key "index"; + description + "List of CPU cores on the system (including logical CPUs + on hyperthreaded systems), keyed by either a numerical + index, or the ALL value for an entry representing the + aggregation across all CPUs."; + + leaf index { + type leafref { + path "../state/index"; + } + description + "Reference to list key"; + } + + container state { + + description + "Operational state data for the system CPU(s)"; + + uses system-cpu-state; + } + } + } + } + + grouping system-top { + description + "Top level system data containers"; + + container system { + description + "Enclosing container for system-related configuration and + operational state data"; + + container config { + description "Global configuration data for the system"; + + uses system-global-config; + + } + + container state { + config false; + description "Global operational state data for the system"; + + uses system-global-config; + uses system-global-state; + } + + uses system-clock-top; + uses system-dns-top; + uses system-ntp-top; + uses oc-sys-mgmt:system-grpc-server-top; + uses oc-sys-term:system-ssh-server-top; + uses oc-sys-term:system-telnet-server-top; + uses oc-log:logging-top; + uses oc-aaa:aaa-top; + uses system-memory-top; + uses system-cpu-top; + uses oc-proc:procmon-processes-top; + uses oc-alarms:alarms-top; + uses oc-messages:messages-top; + } + } + + // data definition statements + + uses system-top; + +} diff --git a/models/yang/sonic/common/sonic-common.yang b/models/yang/sonic/common/sonic-common.yang new file mode 100644 index 000000000..45d608b68 --- /dev/null +++ b/models/yang/sonic/common/sonic-common.yang @@ -0,0 +1,50 @@ + +module sonic-common { + namespace "http://github.com/Azure/sonic-common"; + prefix cmn; + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC common definitions"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + typedef tagging_mode { + type enumeration { + enum untagged; + enum tagged; + enum priority_tagged; + } + } + + typedef admin-status { + type enumeration { + enum up; + enum down; + } + } + + container operation { + description "This definition is used internally by CVL and + is not exposed in NBI. Leaf 'operation' allows + evaluation of must expression for CREATE/UPDATE/DELETE + operation."; + + leaf operation { + type enumeration { + enum NOP; + enum CREATE; + enum UPDATE; + enum DELETE; + } + } + } +} diff --git a/models/yang/sonic/common/sonic-extension.yang b/models/yang/sonic/common/sonic-extension.yang new file mode 100644 index 000000000..f48880958 --- /dev/null +++ b/models/yang/sonic/common/sonic-extension.yang @@ -0,0 +1,61 @@ + +module sonic-extension { + namespace "http://github.com/Azure/sonic-extension"; + prefix sonic-ext; + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC Extension"; + + revision 2019-09-18 { + description + "Initial revision."; + } + + extension custom-handler { + description + "Node should be handled by custom handler"; + argument "name"; + } + + extension db-name { + description + "DB name, e.g. APPL_DB, CONFIG_DB"; + argument "value"; + } + + extension key-delim { + description + "Key delimeter, e.g. - |, :"; + argument "value"; + } + + extension key-pattern { + description + "Key pattern, e.g. - ACL_RULE|{aclname}|{rulename}"; + argument "value"; + } + + extension map-list { + description + "If it is a map list"; + argument "value"; + } + + extension map-leaf { + description + "Map leaf names"; + argument "value"; + } + + extension pf-check { + description + "Platform specific validation"; + argument "handler"; + } +} diff --git a/models/yang/sonic/sonic-acl.yang b/models/yang/sonic/sonic-acl.yang new file mode 100644 index 000000000..fba2aad4a --- /dev/null +++ b/models/yang/sonic/sonic-acl.yang @@ -0,0 +1,221 @@ +module sonic-acl { + namespace "http://github.com/Azure/sonic-acl"; + prefix acl; + yang-version 1.1; + + import ietf-inet-types { + prefix inet; + } + + import sonic-port { + prefix prt; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC ACL"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + container sonic-acl { + + container ACL_TABLE { + + list ACL_TABLE_LIST { + key "aclname"; + + leaf aclname { + type string { + pattern '[a-zA-Z0-9]{1}([-a-zA-Z0-9_]{1,63})' { + error-app-tag aclname-invalid; + } + } + } + + leaf policy_desc { + type string { + length 1..255 { + error-app-tag policy-desc-invalid-length; + } + } + } + + leaf stage { + type enumeration { + enum INGRESS; + enum EGRESS; + } + } + + leaf type { + type enumeration { + enum MIRROR; + enum MIRRORV6; + enum L3; + enum L3V6; + } + } + + leaf-list ports { + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + } + } + + container ACL_RULE { + + list ACL_RULE_LIST { + key "aclname rulename"; + + leaf aclname { + type leafref { + path "../../../ACL_TABLE/ACL_TABLE_LIST/aclname"; + } + } + + leaf rulename { + type string { + pattern '[a-zA-Z0-9]{1}([-a-zA-Z0-9_]{1,63})' { + error-app-tag rulename-invalid; + } + } + } + + leaf PRIORITY { + type uint16 { + range "1..65535"{ + error-app-tag priority-invalid-range; + error-message "Invalid ACL rule priority."; + } + } + } + + leaf RULE_DESCRIPTION { + type string { + length 1..255 { + error-app-tag ruledesc-invalid-length; + } + } + } + + leaf PACKET_ACTION { + mandatory true; + type enumeration { + enum FORWARD; + enum DROP; + enum REDIRECT; + } + } + + leaf IP_TYPE { + mandatory true; + type enumeration { + enum ANY; + enum IP; + enum IPV4; + enum IPV4ANY; + enum NON_IPV4; + enum IPV6ANY; + enum NON_IPV6; + } + } + + leaf IP_PROTOCOL { + type uint8 { + range "1|2|6|17|46|47|51|103|115"; + } + } + + leaf ETHER_TYPE { + type string { + pattern "(0x88CC)|(0x8100)|(0x8915)|(0x0806)|(0x0800)|(0x86DD)|(0x8847)" { + error-message "Invalid ACL Rule Ether Type"; + error-app-tag ether-type-invalid; + } + } + } + + choice ip_src_dst { + case ipv4_src_dst { + when "boolean(IP_TYPE[.='ANY' or .='IP' or .='IPV4' or .='IPV4ANY'])"; + leaf SRC_IP { + mandatory true; + type inet:ipv4-prefix; + } + leaf DST_IP { + mandatory true; + type inet:ipv4-prefix; + } + } + case ipv6_src_dst { + when "boolean(IP_TYPE[.='ANY' or .='IP' or .='IPV6' or .='IPV6ANY'])"; + leaf SRC_IPV6 { + mandatory true; + type inet:ipv6-prefix; + } + leaf DST_IPV6 { + mandatory true; + type inet:ipv6-prefix; + } + } + } + + choice src_port { + case l4_src_port { + leaf L4_SRC_PORT { + type inet:port-number; + } + } + case l4_src_port_range { + leaf L4_SRC_PORT_RANGE { + type string { + pattern "[0-9]{1,5}(-)[0-9]{1,5}" { + error-app-tag src-port-range-invalid; + } + } + } + } + } + + choice dst_port { + case l4_dst_port { + leaf L4_DST_PORT { + type inet:port-number; + } + } + case l4_dst_port_range { + leaf L4_DST_PORT_RANGE { + type string { + pattern "[0-9]{1,5}(-)[0-9]{1,5}" { + error-app-tag dst-port-range-invalid; + } + } + } + } + } + + leaf TCP_FLAGS { + type string { + pattern "0[xX][0-9a-fA-F]{2}[/]0[xX][0-9a-fA-F]{2}" { + error-app-tag tcp-flag-invalid; + } + } + } + + leaf DSCP { + type inet:dscp; + } + } + } + } +} diff --git a/models/yang/sonic/sonic-interface.yang b/models/yang/sonic/sonic-interface.yang new file mode 100644 index 000000000..3d22195fc --- /dev/null +++ b/models/yang/sonic/sonic-interface.yang @@ -0,0 +1,67 @@ +module sonic-interface { + namespace "http://github.com/Azure/sonic-interface"; + prefix sint; + + import ietf-inet-types { + prefix inet; + } + + import sonic-port { + prefix prt; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC INTERFACE"; + + revision 2019-07-02 { + description + "Initial revision."; + } + + container sonic-interface { + + container INTERFACE { + + list INTERFACE_LIST { + key "portname"; + + leaf portname{ + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + /* Add a leafref, once VRF YANG is supported + leaf vrf-name { + type string { + pattern 'Vrf([-a-zA-Z0-9_]{1,60})' { + error-app-tag vrf-name-invalid; + } + } + } + */ + + } + + list INTERFACE_IPADDR_LIST { + key "portname ip_prefix"; + + leaf portname{ + type leafref { + path "/prt:sonic-port/prt:PORT/prt:PORT_LIST/prt:ifname"; + } + } + + leaf ip_prefix { + type inet:ip-prefix; + } + } + } + } +} diff --git a/models/yang/sonic/sonic-port.yang b/models/yang/sonic/sonic-port.yang new file mode 100644 index 000000000..4050bd370 --- /dev/null +++ b/models/yang/sonic/sonic-port.yang @@ -0,0 +1,93 @@ +module sonic-port { + namespace "http://github.com/Azure/sonic-port"; + prefix prt; + + import sonic-common { + prefix cmn; + } + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONIC VLAN"; + + revision 2019-05-15 { + description + "Initial revision."; + } + + + container sonic-port { + + container PORT { + + list PORT_LIST { + key "ifname"; + + leaf ifname { + type string { + pattern "Ethernet([1-3][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[0-9])" { + error-message "Invalid interface name"; + error-app-tag interface-name-invalid; + } + } + } + + leaf index { + type uint16; + mandatory true; + + } + + leaf speed { + type uint64 { + range "1000|10000|25000|40000|50000|100000|400000" { + error-message "Invalid Ethernet interface speed"; + error-app-tag port-speed-invalid; + } + } + } + + leaf valid_speeds { + type string; + } + + leaf alias { + type string { + pattern '[ -~]{0,64}'; + } + } + + leaf description { + type string { + pattern '[ -~]{0,64}'; + } + } + + leaf mtu{ + type uint32 { + range "1312..9216" { + error-message "Invalid MTU value"; + error-app-tag mtu-invalid; + } + } + default 9100; + } + + leaf lanes { + type string; + mandatory true; + } + + leaf admin_status { + type cmn:admin-status; + default "down"; + } + } + } + } +} diff --git a/patches/apply.sh b/patches/apply.sh new file mode 100755 index 000000000..f08483154 --- /dev/null +++ b/patches/apply.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +set -e + +PATCH_DIR=$(dirname $(realpath ${BASH_SOURCE[0]})) + +DEST_DIR=vendor +[ ! -z $1 ] && DEST_DIR=$1 + +if [ ! -d "${DEST_DIR}" ]; then + echo "Unknown DEST_DIR \"${DEST_DIR}\"" + exit 1 +fi + +# Copy some of the packages from go mod download directory into vendor directory. +# It is a workaround for 'go mod vendor' not copying all files + +[ -z ${GO} ] && GO=go +[ -z ${GOPATH} ] && GOPATH=$(${GO} env GOPATH) +PKGPATH=$(echo ${GOPATH} | sed 's/:.*$//g')/pkg/mod + +# Copy package files from GOPATH/pkg/mod to vendor +# $1 = package name, $2 = version, $3... = files +function copy() { + for FILE in "${@:3}"; do + rsync -r --chmod=u+w --exclude=testdata --exclude=*_test.go \ + ${PKGPATH}/$1@$2/${FILE} ${DEST_DIR}/$1/ + done +} + +set -x + +copy github.com/openconfig/ygot v0.6.1-0.20190723223108-724a6b18a922 ygen generator + +copy github.com/openconfig/goyang v0.0.0-20190924211109-064f9690516f . + +copy github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c . + +# Apply patches + +patch -d ${DEST_DIR}/github.com/openconfig -p1 < ${PATCH_DIR}/ygot/ygot.patch + +patch -d ${DEST_DIR}/github.com/openconfig/goyang -p1 < ${PATCH_DIR}/goyang/goyang.patch + +patch -d ${DEST_DIR}/github.com/antchfx/jsonquery -p1 < ${PATCH_DIR}/jsonquery.patch + +patch -d ${DEST_DIR}/github.com/golang/glog -p1 < ${PATCH_DIR}/glog.patch + diff --git a/patches/glog.patch b/patches/glog.patch new file mode 100644 index 000000000..7559d5183 --- /dev/null +++ b/patches/glog.patch @@ -0,0 +1,13 @@ +diff --git a/glog.go b/glog.go +index 54bd7af..dccc3c7 100644 +--- a/glog.go ++++ b/glog.go +@@ -677,7 +677,7 @@ func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoTo + } + data := buf.Bytes() + if !flag.Parsed() { +- os.Stderr.Write([]byte("ERROR: logging before flag.Parse: ")) ++ //os.Stderr.Write([]byte("ERROR: logging before flag.Parse: ")) + os.Stderr.Write(data) + } else if l.toStderr { + os.Stderr.Write(data) diff --git a/patches/goyang/goyang.patch b/patches/goyang/goyang.patch new file mode 100644 index 000000000..3bb642044 --- /dev/null +++ b/patches/goyang/goyang.patch @@ -0,0 +1,530 @@ +diff --git a/README.md b/README.md +index 4d22c1e..805adb5 100644 +--- a/README.md ++++ b/README.md +@@ -14,6 +14,7 @@ The forms include: + + * tree - a simple tree representation + * types - list understood types extracted from the schema ++* annotate - a template file to annotate the yang modules + + The yang package, and the goyang program, are not complete and are a work in + progress. +diff --git a/annotate.go b/annotate.go +new file mode 100644 +index 0000000..243c416 +--- /dev/null ++++ b/annotate.go +@@ -0,0 +1,395 @@ ++// Copyright 2015 Google Inc. ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++ ++package main ++ ++import ( ++ "fmt" ++ "io" ++ "strings" ++ ++ "github.com/openconfig/goyang/pkg/yang" ++) ++ ++var allimports = make(map[string]string) ++var modules = make(map[string]*yang.Module) ++var allmodules = make(map[string]*yang.Module) ++ ++func init() { ++ register(&formatter{ ++ name: "annotate", ++ f: genAnnotate, ++ utilf: getFile, ++ help: "generate template file for yang annotations", ++ }) ++} ++ ++// Get the modules for which annotation file needs to be generated ++func getFile(files []string, mods map[string]*yang.Module) { ++ allmodules = mods ++ for _, name := range files { ++ slash := strings.Split(name, "/") ++ modname := slash[len(slash)-1] ++ modname = strings.TrimSuffix(modname, ".yang"); ++ /* Save the yang.Module entries we are interested in */ ++ modules[modname] = mods[modname] ++ } ++} ++ ++func genAnnotate(w io.Writer, entries []*yang.Entry) { ++ /* Get all the imported modules in the entries */ ++ GetAllImports(entries) ++ for _, e := range entries { ++ if _, ok := modules[e.Name]; ok { ++ var path string = "" ++ var prefix string = "" ++ generate(w, e, path, prefix) ++ // { Add closing brace for each module ++ fmt.Fprintln(w, "}") ++ fmt.Fprintln(w) ++ } ++ } ++} ++ ++// generate writes to stdoutput a template annotation file entry for the selected modules. ++func generate(w io.Writer, e *yang.Entry, path string, prefix string) { ++ if e.Parent == nil { ++ if e.Name != "" { ++ fmt.Fprintf(w, "module %s-annot {\n", e.Name) //} ++ fmt.Fprintln(w) ++ fmt.Fprintf(w, " yang-version \"%s\";\n", getYangVersion(e.Name, modules)) ++ fmt.Fprintln(w) ++ fmt.Fprintf(w, " namespace \"http://openconfig.net/yang/annotation/%s-annot\";\n", e.Prefix.Name) ++ if e.Prefix != nil { ++ fmt.Fprintf(w, " prefix \"%s-annot\";\n", e.Prefix.Name) ++ } ++ fmt.Fprintln(w) ++ ++ var imports = make(map[string]string) ++ imports = getImportModules(e.Name, modules) ++ for k := range imports { ++ if e.Name != k { ++ fmt.Fprintf(w, " import %s { prefix %s; }\n", k, allimports[k]) ++ } ++ } ++ // Include the module for which annotation is being generated ++ fmt.Fprintf(w, " import %s { prefix %s; }\n", e.Name, e.Prefix.Name) ++ ++ fmt.Fprintln(w) ++ } ++ } ++ ++ name := e.Name ++ if prefix == "" && e.Prefix != nil { ++ prefix = e.Prefix.Name ++ } ++ name = prefix + ":" + name ++ ++ if (e.Node.Kind() != "module") { ++ path = path + "/" + name ++ printDeviation(w, path) ++ } ++ ++ var names []string ++ for k := range e.Dir { ++ names = append(names, k) ++ } ++ ++ if (e.Node.Kind() == "module") { ++ if len(e.Node.(*yang.Module).Augment) > 0 { ++ for _,a := range e.Node.(*yang.Module).Augment { ++ pathList := strings.Split(a.Name, "/") ++ pathList = pathList[1:] ++ for i, pvar := range pathList { ++ if len(pvar) > 0 && !strings.Contains(pvar, ":") { ++ pvar = e.Prefix.Name + ":" + pvar ++ pathList[i] = pvar ++ } ++ } ++ path = "/" + strings.Join(pathList, "/") ++ handleAugments(w, a, e.Node.(*yang.Module).Grouping, e.Prefix.Name, path) ++ } ++ } ++ } ++ ++ for _, k := range names { ++ generate(w, e.Dir[k], path, prefix) ++ } ++ ++} ++ ++func printDeviation(w io.Writer, path string){ ++ fmt.Fprintf(w, " deviation %s {\n", path) ++ fmt.Fprintf(w, " deviate add {\n") ++ fmt.Fprintf(w, " }\n") ++ fmt.Fprintf(w, " }\n") ++ fmt.Fprintln(w) ++} ++ ++ ++// Save to map all imported modules ++func GetAllImports(entries []*yang.Entry) { ++ for _, e := range entries { ++ allimports[e.Name] = e.Prefix.Name ++ } ++} ++ ++func GetModuleFromPrefix(prefix string) string { ++ for m, p := range allimports { ++ if prefix == p { ++ return m ++ } ++ } ++ return "" ++} ++ ++//Get Yang version from the yang.Modules ++func getYangVersion(modname string, mods map[string]*yang.Module) string { ++ if (mods[modname].YangVersion != nil) { ++ return mods[modname].YangVersion.Name ++ } ++ return "" ++ ++} ++ ++// Get imported modules for a given module from yang.Module ++func getImportModules(modname string, mods map[string]*yang.Module) map[string]string { ++ imports := map[string]string{} ++ if (mods[modname].Import != nil) { ++ for _, imp := range mods[modname].Import { ++ imports[imp.Name] = imp.Prefix.Name ++ } ++ } ++ return imports ++} ++ ++func handleAugments(w io.Writer, a *yang.Augment, grp []*yang.Grouping, prefix string, path string) { ++ for _, u := range a.Uses { ++ grpN := u.Name ++ for _, g := range grp { ++ if grpN == g.Name { ++ if len(g.Container) > 0 { ++ handleContainer(w, g.Container, grp, prefix, path) ++ } ++ if len(g.List) > 0 { ++ handleList(w, g.List, grp, prefix, path) ++ } ++ if len(g.LeafList) > 0 { ++ handleLeafList(w, g.LeafList, prefix, path) ++ } ++ if len(g.Leaf) > 0 { ++ handleLeaf(w, g.Leaf, prefix, path) ++ } ++ if len(g.Choice) > 0 { ++ handleChoice(w, g.Choice, grp, prefix, path) ++ } ++ if len(g.Uses) > 0 { ++ handleUses(w, g.Uses, grp, prefix, path) ++ } ++ } ++ } ++ } ++ ++} ++ ++func handleUses(w io.Writer, u []*yang.Uses, grp []*yang.Grouping, prefix string, path string) { ++ for _, u := range u { ++ grpN := u.Name ++ if strings.Contains(grpN, ":") { ++ tokens := strings.Split(grpN, ":") ++ nprefix := tokens[0] ++ grpN = tokens[1] ++ mod := GetModuleFromPrefix(nprefix) ++ grp = allmodules[mod].Grouping ++ } ++ for _, g := range grp { ++ if grpN == g.Name { ++ if len(g.Container) > 0 { ++ handleContainer(w, g.Container, grp, prefix, path) ++ } ++ if len(g.List) > 0 { ++ handleList(w, g.List, grp, prefix, path) ++ } ++ if len(g.LeafList) > 0 { ++ handleLeafList(w, g.LeafList, prefix, path) ++ } ++ if len(g.Leaf) > 0 { ++ handleLeaf(w, g.Leaf, prefix, path) ++ } ++ if len(g.Choice) > 0 { ++ handleChoice(w, g.Choice, grp, prefix, path) ++ } ++ if len(g.Uses) > 0 { ++ handleUses(w, g.Uses, grp, prefix, path) ++ } ++ ++ } ++ } ++ } ++ ++} ++ ++func handleContainer(w io.Writer, ctr []*yang.Container, grp []*yang.Grouping, prefix string, path string) { ++ for _, c := range ctr { ++ npath := path + "/" + prefix + ":" + c.Name ++ printDeviation(w, npath) ++ if len(c.Container) > 0 { ++ handleContainer(w, c.Container, grp, prefix, npath) ++ } ++ if len(c.List) > 0 { ++ handleList(w, c.List, grp, prefix, npath) ++ } ++ if len(c.LeafList) > 0 { ++ handleLeafList(w, c.LeafList, prefix, npath) ++ } ++ if len(c.Leaf) > 0 { ++ handleLeaf(w, c.Leaf, prefix, npath) ++ } ++ if len(c.Choice) > 0 { ++ handleChoice(w, c.Choice, grp, prefix, npath) ++ } ++ if len(c.Grouping) > 0 { ++ handleGrouping(w, c.Grouping, grp, prefix, npath) ++ } ++ if len(c.Uses) > 0 { ++ handleUses(w, c.Uses, grp, prefix, npath) ++ } ++ } ++} ++ ++func handleList(w io.Writer, lst []*yang.List, grp []*yang.Grouping, prefix string, path string) { ++ for _, l := range lst { ++ npath := path + "/" + prefix + ":" + l.Name ++ printDeviation(w, npath) ++ if len(l.Container) > 0 { ++ handleContainer(w, l.Container, grp, prefix, npath) ++ } ++ if len(l.List) > 0 { ++ handleList(w, l.List, grp, prefix, npath) ++ } ++ if len(l.LeafList) > 0 { ++ handleLeafList(w, l.LeafList, prefix, npath) ++ } ++ if len(l.Leaf) > 0 { ++ handleLeaf(w, l.Leaf, prefix, npath) ++ } ++ if len(l.Choice) > 0 { ++ handleChoice(w, l.Choice, grp, prefix, npath) ++ } ++ if len(l.Grouping) > 0 { ++ handleGrouping(w, l.Grouping, grp, prefix, npath) ++ } ++ if len(l.Uses) > 0 { ++ handleUses(w, l.Uses, grp, prefix, npath) ++ } ++ ++ } ++} ++ ++func handleGrouping(w io.Writer, grp []*yang.Grouping, grptop []*yang.Grouping, prefix string, path string) { ++ for _, g := range grp { ++ npath := path + "/" + prefix + ":" + g.Name ++ printDeviation(w, npath) ++ if len(g.Container) > 0 { ++ handleContainer(w, g.Container, grptop, prefix, npath) ++ } ++ if len(g.List) > 0 { ++ handleList(w, g.List, grptop, prefix, npath) ++ } ++ if len(g.LeafList) > 0 { ++ handleLeafList(w, g.LeafList, prefix, npath) ++ } ++ if len(g.Leaf) > 0 { ++ handleLeaf(w, g.Leaf, prefix, npath) ++ } ++ if len(g.Choice) > 0 { ++ handleChoice(w, g.Choice, grptop, prefix, npath) ++ } ++ if len(g.Grouping) > 0 { ++ handleGrouping(w, g.Grouping, grptop, prefix, npath) ++ } ++ if len(g.Uses) > 0 { ++ handleUses(w, g.Uses, grptop, prefix, npath) ++ } ++ ++ } ++} ++ ++func handleLeaf (w io.Writer, lf []*yang.Leaf, prefix string, path string) { ++ if len(lf) > 0 { ++ for _, l := range lf { ++ npath := path + "/" + prefix + ":" + l.Name ++ printDeviation(w, npath) ++ } ++ } ++ ++} ++ ++func handleLeafList (w io.Writer, llst []*yang.LeafList, prefix string, path string) { ++ if len(llst) > 0 { ++ for _, l := range llst { ++ npath := path + "/" + prefix + ":" + l.Name ++ printDeviation(w, npath) ++ } ++ } ++} ++ ++func handleChoice (w io.Writer, ch []*yang.Choice, grp []*yang.Grouping, prefix string, path string) { ++ for _, c := range ch { ++ npath := path + "/" + prefix + ":" + c.Name ++ printDeviation(w, npath) ++ if len(c.Container) > 0 { ++ handleContainer(w, c.Container, grp, prefix, npath) ++ } ++ if len(c.List) > 0 { ++ handleList(w, c.List, grp, prefix, npath) ++ } ++ if len(c.LeafList) > 0 { ++ handleLeafList(w, c.LeafList, prefix, npath) ++ } ++ if len(c.Leaf) > 0 { ++ handleLeaf(w, c.Leaf, prefix, npath) ++ } ++ if len(c.Case) > 0 { ++ handleCase(w, c.Case, grp, prefix, npath) ++ } ++ } ++} ++ ++func handleCase (w io.Writer, ch []*yang.Case, grp []*yang.Grouping, prefix string, path string) { ++ for _, c := range ch { ++ npath := path + "/" + prefix + ":" + c.Name ++ printDeviation(w, npath) ++ if len(c.Container) > 0 { ++ handleContainer(w, c.Container, grp, prefix, npath) ++ } ++ if len(c.List) > 0 { ++ handleList(w, c.List, grp, prefix, npath) ++ } ++ if len(c.LeafList) > 0 { ++ handleLeafList(w, c.LeafList, prefix, npath) ++ } ++ if len(c.Leaf) > 0 { ++ handleLeaf(w, c.Leaf, prefix, npath) ++ } ++ if len(c.Choice) > 0 { ++ handleChoice(w, c.Choice, grp, prefix, npath) ++ } ++ if len(c.Uses) > 0 { ++ handleUses(w, c.Uses, grp, prefix, npath) ++ } ++ ++ } ++} ++ +diff --git a/pkg/yang/entry.go b/pkg/yang/entry.go +index ef658d6..f626dc9 100644 +--- a/pkg/yang/entry.go ++++ b/pkg/yang/entry.go +@@ -80,6 +80,7 @@ type Entry struct { + + // Fields associated with directory nodes + Dir map[string]*Entry `json:",omitempty"` ++ DirOKeys []string // Ordered Keys list in Dir + Key string `json:",omitempty"` // Optional key name for lists (i.e., maps) + + // Fields associated with leaf nodes +@@ -115,6 +116,10 @@ type Entry struct { + // the augmenting entity per RFC6020 Section 7.15.2. The namespace + // of the Entry should be accessed using the Namespace function. + namespace *Value ++ ++ ChildSchemaCache map[reflect.StructTag]*Entry `json:"-"` ++ ++ IsSchemaValidated bool `json:"-"` + } + + // An RPCEntry contains information related to an RPC Node. +@@ -264,6 +269,7 @@ func newDirectory(n Node) *Entry { + return &Entry{ + Kind: DirectoryEntry, + Dir: make(map[string]*Entry), ++ DirOKeys: make([]string, 0), + Node: n, + Name: n.NName(), + Extra: map[string][]interface{}{}, +@@ -366,6 +372,7 @@ func (e *Entry) add(key string, value *Entry) *Entry { + return e + } + e.Dir[key] = value ++ e.DirOKeys = append(e.DirOKeys, key) + return e + } + +@@ -1090,6 +1097,7 @@ func (e *Entry) FixChoice() { + } + ce.Parent = ne + e.Dir[k] = ne ++ e.DirOKeys = append(e.DirOKeys, k) + } + } + } +@@ -1260,6 +1268,14 @@ func (e *Entry) shallowDup() *Entry { + // copied we will have to explicitly uncopy them. + ne := *e + ++ //Copy the ordered Dir keys to new entry ++ if len(e.DirOKeys) > 0 { ++ ne.DirOKeys = make([]string, 0) ++ for _, key := range e.DirOKeys { ++ ne.DirOKeys = append(ne.DirOKeys, key) ++ } ++ } ++ + // Now only copy direct children, clear their Dir, and fix up + // Parent pointers. + if e.Dir != nil { +@@ -1283,6 +1299,14 @@ func (e *Entry) dup() *Entry { + // to do that. + ne := *e + ++ //Copy the ordered Dir keys to new entry ++ if len(e.DirOKeys) > 0 { ++ ne.DirOKeys = make([]string, 0) ++ for _, key := range e.DirOKeys { ++ ne.DirOKeys = append(ne.DirOKeys, key) ++ } ++ } ++ + // Now recurse down to all of our children, fixing up Parent + // pointers as we go. + if e.Dir != nil { +@@ -1317,6 +1341,7 @@ func (e *Entry) merge(prefix *Value, namespace *Value, oe *Entry) { + } else { + v.Parent = e + e.Dir[k] = v ++ e.DirOKeys = append(e.DirOKeys, k) + } + } + } +@@ -1378,8 +1403,8 @@ func (s sortedErrors) Less(i, j int) bool { + } + return nless(fi[x], fj[x]) + } +- for x := 1; x < 4; x++ { +- switch compare(1) { ++ for x := 0; x < len(fi) && x < len(fj); x++ { ++ switch compare(x) { + case -1: + return true + case 1: +diff --git a/yang.go b/yang.go +index 2480a4e..515d1b3 100644 +--- a/yang.go ++++ b/yang.go +@@ -58,6 +58,7 @@ import ( + type formatter struct { + name string + f func(io.Writer, []*yang.Entry) ++ utilf func([]string, map[string]*yang.Module) + help string + flags *getopt.Set + } +@@ -208,5 +209,8 @@ Formats: + entries[x] = yang.ToEntry(mods[n]) + } + ++ if format == "annotate" { ++ formatters[format].utilf(files, mods) ++ } + formatters[format].f(os.Stdout, entries) + } diff --git a/patches/jsonquery.patch b/patches/jsonquery.patch new file mode 100644 index 000000000..122ef8e01 --- /dev/null +++ b/patches/jsonquery.patch @@ -0,0 +1,14 @@ +diff --git a/node.go b/node.go +index 76032bb..db73a1e 100644 +--- a/node.go ++++ b/node.go +@@ -155,3 +155,9 @@ func Parse(r io.Reader) (*Node, error) { + } + return parse(b) + } ++ ++func ParseJsonMap(jsonMap *map[string]interface{}) (*Node, error) { ++ doc := &Node{Type: DocumentNode} ++ parseValue(*jsonMap, doc, 1) ++ return doc, nil ++} diff --git a/patches/ygot/ygot.patch b/patches/ygot/ygot.patch new file mode 100644 index 000000000..01aaa1a5f --- /dev/null +++ b/patches/ygot/ygot.patch @@ -0,0 +1,752 @@ +diff -ruN ygot-dir-orig/ygot/util/debug.go ygot-dir/ygot/util/debug.go +--- ygot-dir-orig/ygot/util/debug.go 2019-10-24 12:30:06.378629000 -0700 ++++ ygot-dir/ygot/util/debug.go 2019-10-24 12:31:25.059277000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package util + + import ( +@@ -53,6 +56,14 @@ + fmt.Println(globalIndent + out) + } + ++func IsDebugLibraryEnabled () bool { ++ return debugLibrary ++} ++ ++func IsDebugSchemaEnabled () bool { ++ return debugSchema ++} ++ + // DbgSchema prints v if the package global variable debugSchema is set. + // v has the same format as Printf. + func DbgSchema(v ...interface{}) { +@@ -177,6 +188,9 @@ + + // YangTypeToDebugString returns a debug string representation of a YangType. + func YangTypeToDebugString(yt *yang.YangType) string { ++ if !debugLibrary { ++ return "" ++ } + out := fmt.Sprintf("(TypeKind: %s", yang.TypeKindToName[yt.Kind]) + if len(yt.Pattern) != 0 { + out += fmt.Sprintf(", Pattern: %s", strings.Join(yt.Pattern, " or ")) +diff -ruN ygot-dir-orig/ygot/util/reflect.go ygot-dir/ygot/util/reflect.go +--- ygot-dir-orig/ygot/util/reflect.go 2019-10-24 12:30:06.403914000 -0700 ++++ ygot-dir/ygot/util/reflect.go 2019-10-24 12:31:25.063424000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package util + + import ( +@@ -196,8 +199,10 @@ + + // InsertIntoMap inserts value with key into parent which must be a map. + func InsertIntoMap(parentMap interface{}, key interface{}, value interface{}) error { +- DbgPrint("InsertIntoMap into parent type %T with key %v(%T) value \n%s\n (%T)", +- parentMap, ValueStrDebug(key), key, pretty.Sprint(value), value) ++ if debugLibrary { ++ DbgPrint("InsertIntoMap into parent type %T with key %v(%T) value \n%s\n (%T)", ++ parentMap, ValueStrDebug(key), key, pretty.Sprint(value), value) ++ } + + v := reflect.ValueOf(parentMap) + t := reflect.TypeOf(parentMap) +@@ -288,7 +293,7 @@ + n = reflect.Zero(ft.Type) + } + +- if !isFieldTypeCompatible(ft, n) { ++ if !isFieldTypeCompatible(ft, n) && !IsValueTypeCompatible(ft.Type, v) { + return fmt.Errorf("cannot assign value %v (type %T) to struct field %s (type %v) in struct %T", fieldValue, fieldValue, fieldName, ft.Type, parentStruct) + } + +diff -ruN ygot-dir-orig/ygot/util/schema.go ygot-dir/ygot/util/schema.go +--- ygot-dir-orig/ygot/util/schema.go 2019-10-24 12:30:06.417942000 -0700 ++++ ygot-dir/ygot/util/schema.go 2019-10-24 12:31:25.069042000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package util + + import ( +@@ -22,6 +25,8 @@ + "github.com/openconfig/goyang/pkg/yang" + ) + ++var schemaPathCache map[reflect.StructTag][][]string = make(map[reflect.StructTag][][]string) ++ + // IsLeafRef reports whether schema is a leafref schema node type. + func IsLeafRef(schema *yang.Entry) bool { + if schema == nil || schema.Type == nil { +@@ -68,17 +73,22 @@ + + // SchemaPaths returns all the paths in the path tag. + func SchemaPaths(f reflect.StructField) ([][]string, error) { +- var out [][]string +- pathTag, ok := f.Tag.Lookup("path") +- if !ok || pathTag == "" { +- return nil, fmt.Errorf("field %s did not specify a path", f.Name) +- } +- +- ps := strings.Split(pathTag, "|") +- for _, p := range ps { +- out = append(out, StripModulePrefixes(strings.Split(p, "/"))) ++ if tmpOut, ok := schemaPathCache[f.Tag]; ok { ++ return tmpOut, nil ++ } else { ++ var out [][]string ++ pathTag, ok := f.Tag.Lookup("path") ++ if !ok || pathTag == "" { ++ return nil, fmt.Errorf("field %s did not specify a path", f.Name) ++ } ++ ++ ps := strings.Split(pathTag, "|") ++ for _, p := range ps { ++ out = append(out, StripModulePrefixes(strings.Split(p, "/"))) ++ } ++ schemaPathCache[f.Tag] = out ++ return out, nil + } +- return out, nil + } + + // ChildSchema returns the first child schema that matches path from the given +@@ -233,7 +243,9 @@ + found := true + DbgSchema("traversing schema Dirs...") + for ; len(p) > 0; p = p[1:] { +- DbgSchema("/%s", p[0]) ++ if IsDebugSchemaEnabled() { ++ DbgSchema("/%s", p[0]) ++ } + var ok bool + s, ok = s.Dir[p[0]] + if !ok { +@@ -261,10 +273,13 @@ + return nil, nil + } + entries := FindFirstNonChoiceOrCase(schema) +- +- DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) ++ if IsDebugSchemaEnabled() { ++ DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) ++ } + for pe, entry := range entries { +- DbgSchema("%s ? ", pe) ++ if IsDebugSchemaEnabled() { ++ DbgSchema("%s ? ", pe) ++ } + if pe == p[0] { + DbgSchema(" - match\n") + return entry, nil +diff -ruN ygot-dir-orig/ygot/ytypes/container.go ygot-dir/ygot/ytypes/container.go +--- ygot-dir-orig/ygot/ytypes/container.go 2019-10-24 12:30:07.700737000 -0700 ++++ ygot-dir/ygot/ytypes/container.go 2019-10-24 12:31:26.682226000 -0700 +@@ -12,12 +12,15 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( + "fmt" + "reflect" +- ++ + "github.com/kylelemons/godebug/pretty" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/util" +@@ -71,7 +74,7 @@ + if errs := Validate(cschema, fieldValue); errs != nil { + errors = util.AppendErrs(errors, util.PrefixErrors(errs, cschema.Path())) + } +- case !util.IsValueNilOrDefault(structElems.Field(i).Interface()): ++ case !structElems.Field(i).IsNil(): + // Either an element in choice schema subtree, or bad field. + // If the former, it will be found in the choice check below. + extraFields[fieldName] = nil +@@ -217,7 +220,10 @@ + } + } + +- util.DbgPrint("container after unmarshal:\n%s\n", pretty.Sprint(destv.Interface())) ++ if util.IsDebugLibraryEnabled() { ++ util.DbgPrint("container after unmarshal:\n%s\n", pretty.Sprint(destv.Interface())) ++ } ++ + return nil + } + +diff -ruN ygot-dir-orig/ygot/ytypes/leaf.go ygot-dir/ygot/ytypes/leaf.go +--- ygot-dir-orig/ygot/ytypes/leaf.go 2019-10-24 12:30:07.705496000 -0700 ++++ ygot-dir/ygot/ytypes/leaf.go 2019-10-24 12:31:26.691433000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -79,7 +82,7 @@ + + switch ykind { + case yang.Ybinary: +- return util.NewErrs(validateBinary(schema, value)) ++ return util.NewErrs(validateBinary(schema, rv)) + case yang.Ybits: + return nil + // TODO(mostrowski): restore when representation is decided. +@@ -252,7 +255,7 @@ + // during validation against each matching schema otherwise. + func validateMatchingSchemas(schema *yang.Entry, value interface{}) util.Errors { + var errors []error +- ss := findMatchingSchemasInUnion(schema.Type, value) ++ ss := findMatchingSchemasInUnion(schema, schema.Type, value) + var kk []yang.TypeKind + for _, s := range ss { + kk = append(kk, s.Type.Kind) +@@ -283,17 +286,25 @@ + // findMatchingSchemasInUnion returns all schemas in the given union type, + // including those within nested unions, that match the Go type of value. + // value must not be nil. +-func findMatchingSchemasInUnion(ytype *yang.YangType, value interface{}) []*yang.Entry { ++func findMatchingSchemasInUnion(schema *yang.Entry, ytype *yang.YangType, value interface{}) []*yang.Entry { + var matches []*yang.Entry + + util.DbgPrint("findMatchingSchemasInUnion for type %T, kind %s", value, reflect.TypeOf(value).Kind()) + for _, t := range ytype.Type { + if t.Kind == yang.Yunion { + // Recursively check all union types within this union. +- matches = append(matches, findMatchingSchemasInUnion(t, value)...) ++ matches = append(matches, findMatchingSchemasInUnion(schema, t, value)...) + continue + } + ++ if t.Kind == yang.Yleafref { ++ ns, err := findLeafRefSchema(schema, t.Path) ++ if err != nil { ++ log.Warningf("not found base Go type for type %v in union value %s", t.Kind, util.ValueStr(value)) ++ continue ++ } ++ t = ns.Type ++ } + ybt := yangBuiltinTypeToGoType(t.Kind) + if reflect.ValueOf(value).Kind() == reflect.Ptr { + ybt = ygot.ToPtr(yangBuiltinTypeToGoType(t.Kind)) +@@ -418,12 +429,10 @@ + return nil + } + +-// YANGEmpty is a derived type which is used to represent the YANG empty type. ++// YANGEmpty is a derived type which is used to represent the YANG ++// empty type. + type YANGEmpty bool + +-// Binary is a derived type which is used to represent the YANG binary type. +-type Binary []byte +- + // unmarshalLeaf unmarshals a scalar value (determined by json.Unmarshal) into + // the parent containing the leaf. + // schema points to the schema for the leaf type. +@@ -720,7 +729,9 @@ + return nil, fmt.Errorf("%s ΛEnumTypes function returned wrong type %T, want map[string][]reflect.Type", t, ei) + } + +- util.DbgPrint("path is %s for schema %s", absoluteSchemaDataPath(schema), schema.Name) ++ if util.IsDebugLibraryEnabled() { ++ util.DbgPrint("path is %s for schema %s", absoluteSchemaDataPath(schema), schema.Name) ++ } + + return enumTypesMap[absoluteSchemaDataPath(schema)], nil + } +diff -ruN ygot-dir-orig/ygot/ytypes/list.go ygot-dir/ygot/ytypes/list.go +--- ygot-dir-orig/ygot/ytypes/list.go 2019-10-24 12:30:07.712731000 -0700 ++++ ygot-dir/ygot/ytypes/list.go 2019-10-24 12:31:26.696852000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -217,6 +220,9 @@ + if len(schema.Key) == 0 { + return fmt.Errorf("list %s with config set must have a key", schema.Name) + } ++ if schema.IsSchemaValidated == true { ++ return nil ++ } + keys := strings.Split(schema.Key, " ") + keysMissing := make(map[string]bool) + for _, v := range keys { +@@ -232,6 +238,7 @@ + } + } + ++ schema.IsSchemaValidated = true + return nil + } + +@@ -282,10 +289,10 @@ + if util.IsValueNil(jsonList) { + return nil + } +- // Check that the schema itself is valid. ++ + if err := validateListSchema(schema); err != nil { + return err +- } ++ } + + util.DbgPrint("unmarshalList jsonList %v, type %T, into parent type %T, schema name %s", util.ValueStrDebug(jsonList), jsonList, parent, schema.Name) + +@@ -350,7 +357,9 @@ + return err + } + } +- util.DbgPrint("list after unmarshal:\n%s\n", pretty.Sprint(parent)) ++ if util.IsDebugLibraryEnabled() { ++ util.DbgPrint("list after unmarshal:\n%s\n", pretty.Sprint(parent)) ++ } + + return nil + } +@@ -388,17 +397,96 @@ + if err != nil { + return err + } +- + fv := val.Elem().FieldByName(fn) + ft := fv.Type() + if util.IsValuePtr(fv) { + ft = ft.Elem() + } +- +- nv, err := StringToType(ft, fieldVal) ++ sf, ok := val.Elem().Type().FieldByName(fn) ++ if ok == false { ++ return fmt.Errorf("Field %s not present in the struct %s", fn, val.Elem()) ++ } ++ cschema, err := childSchema(schema, sf) + if err != nil { + return err + } ++ keyLeafKind := cschema.Type.Kind ++ if keyLeafKind == yang.Yleafref { ++ lrfschema, err := resolveLeafRef(cschema) ++ if err != nil { ++ return err ++ } ++ keyLeafKind = lrfschema.Type.Kind ++ } ++ ++ var nv reflect.Value ++ if keyLeafKind == yang.Yunion && strings.HasSuffix(keyT.Name(), "_Union") { ++ sks, err := getUnionKindsNotEnums(cschema) ++ if err != nil { ++ return err ++ } ++ for _, sk := range sks { ++ gv, err := StringToType(reflect.TypeOf(yangBuiltinTypeToGoType(sk)), fieldVal) ++ if err == nil { ++ mn := "To_" + ft.Name() ++ mapMethod := val.MethodByName(mn) ++ if !mapMethod.IsValid() { ++ return fmt.Errorf("%s does not have a %s function", val, mn) ++ } ++ ec := mapMethod.Call([]reflect.Value{gv}) ++ if len(ec) != 2 { ++ return fmt.Errorf("%s %s function returns %d params", ft.Name(), mn, len(ec)) ++ } ++ ei := ec[0].Interface() ++ ee := ec[1].Interface() ++ if ee != nil { ++ return fmt.Errorf("unmarshaled %v type %T does not have a union type: %v", fieldVal, fieldVal, ee) ++ } ++ nv = reflect.ValueOf(ei) ++ break ++ } ++ } ++ ++ if nv.IsValid() == false { ++ ets, err := schemaToEnumTypes(cschema, elmT) ++ if err != nil { ++ return err ++ } ++ for _, et := range ets { ++ ev, err := castToEnumValue(et, fieldVal) ++ if err != nil { ++ return err ++ } ++ if ev != nil { ++ mn := "To_" + ft.Name() ++ mapMethod := val.MethodByName(mn) ++ if !mapMethod.IsValid() { ++ return fmt.Errorf("%s does not have a %s function", val, mn) ++ } ++ ec := mapMethod.Call([]reflect.Value{reflect.ValueOf(ev)}) ++ if len(ec) != 2 { ++ return fmt.Errorf("%s %s function returns %d params", ft.Name(), mn, len(ec)) ++ } ++ ei := ec[0].Interface() ++ ee := ec[1].Interface() ++ if ee != nil { ++ return fmt.Errorf("unmarshaled %v type %T does not have a union type: %v", fieldVal, fieldVal, ee) ++ } ++ nv = reflect.ValueOf(ei) ++ break ++ } ++ fmt.Errorf("could not unmarshal %v into enum type: %s\n", fieldVal, err) ++ } ++ if nv.IsValid() == false { ++ return fmt.Errorf("could not create the value type for the field name %s with the value %s", fn, fieldVal) ++ } ++ } ++ } else { ++ nv, err = StringToType(ft, fieldVal) ++ if err != nil { ++ return err ++ } ++ } + return util.InsertIntoStruct(val.Interface(), fn, nv.Interface()) + } + +@@ -494,6 +582,9 @@ + } + + // TODO(yusufsn): When the key is a leafref, its target should be filled out. ++ if (len(keys) == 0) { ++ return nil, nil ++ } + mapVal, err := makeValForInsert(schema, root, keys) + if err != nil { + return nil, fmt.Errorf("failed to create map value for insert, root %T, keys %v: %v", root, keys, err) +diff -ruN ygot-dir-orig/ygot/ytypes/node.go ygot-dir/ygot/ytypes/node.go +--- ygot-dir-orig/ygot/ytypes/node.go 2019-10-24 12:30:07.727365000 -0700 ++++ ygot-dir/ygot/ytypes/node.go 2019-10-24 12:31:26.701328000 -0700 +@@ -12,17 +12,19 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +- "reflect" +- + "github.com/golang/protobuf/proto" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/util" + "github.com/openconfig/ygot/ygot" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ++ "reflect" + + gpb "github.com/openconfig/gnmi/proto/gnmi" + ) +@@ -129,6 +131,16 @@ + if err := util.InitializeStructField(root, ft.Name); err != nil { + return nil, status.Errorf(codes.Unknown, "failed to initialize struct field %s in %T, child schema %v, path %v", ft.Name, root, cschema, path) + } ++ ++ if cschema.IsLeaf() || cschema.IsLeafList() { ++ if len(path.Elem) == 1 && len(path.Elem[0].Key) == 1 { ++ var vals []string ++ vals = append(vals, path.Elem[0].Key[path.Elem[0].Name]) ++ if args.val, err = ygot.EncodeTypedValue(vals, gpb.Encoding_JSON_IETF); err != nil { ++ return nil, status.Errorf(codes.Unknown, "failed to get the typed value '%v' for leaf/leaf-list => %s in %T ; because of %v", vals, ft.Name, root, err) ++ } ++ } ++ } + } + + // If val in args is set to a non-nil value and the path is exhausted, we +@@ -286,6 +298,11 @@ + if err != nil { + return nil, err + } ++ ++ if (key == nil) { ++ return []*TreeNode{{Path: traversedPath,Schema: schema,Data: root,}}, nil ++ } ++ + nodes, err := retrieveNode(schema, rv.MapIndex(reflect.ValueOf(key)).Interface(), util.PopGNMIPath(path), appendElem(traversedPath, path.GetElem()[0]), args) + if err != nil { + return nil, err +diff -ruN ygot-dir-orig/ygot/ytypes/string_type.go ygot-dir/ygot/ytypes/string_type.go +--- ygot-dir-orig/ygot/ytypes/string_type.go 2019-10-24 12:30:07.734288000 -0700 ++++ ygot-dir/ygot/ytypes/string_type.go 2019-10-24 12:31:26.705649000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -23,6 +26,8 @@ + "github.com/openconfig/goyang/pkg/yang" + ) + ++var regexpCache map[string]*regexp.Regexp = make(map[string]*regexp.Regexp) ++ + // Refer to: https://tools.ietf.org/html/rfc6020#section-9.4. + + // validateString validates value, which must be a Go string type, against the +@@ -48,10 +53,18 @@ + + // Check that the value satisfies any regex patterns. + for _, p := range schema.Type.Pattern { +- r, err := regexp.Compile(fixYangRegexp(p)) +- if err != nil { +- return err ++ var r *regexp.Regexp ++ if val, ok := regexpCache[p]; ok { ++ r = val ++ } else { ++ var err error ++ r, err = regexp.Compile(fixYangRegexp(p)) ++ if err != nil { ++ return err ++ } ++ regexpCache[p] = r + } ++ + // fixYangRegexp adds ^(...)$ around the pattern - the result is + // equivalent to a full match of whole string. + if !r.MatchString(stringVal) { +@@ -105,13 +118,29 @@ + return fmt.Errorf("string schema %s has wrong type %v", schema.Name, schema.Type.Kind) + } + ++ if schema.IsSchemaValidated { ++ return nil ++ } ++ ++ var err error ++ + for _, p := range schema.Type.Pattern { +- if _, err := regexp.Compile(fixYangRegexp(p)); err != nil { +- return fmt.Errorf("error generating regexp %s %v for schema %s", p, err, schema.Name) +- } ++ _, ok := regexpCache[p] ++ if (ok == false) { ++ var r *regexp.Regexp ++ if r, err = regexp.Compile(fixYangRegexp(p)); err != nil { ++ return fmt.Errorf("error generating regexp %s %v for schema %s", p, err, schema.Name) ++ } else { ++ regexpCache[p] = r ++ } ++ } + } + +- return validateLengthSchema(schema) ++ if err = validateLengthSchema(schema); err == nil { ++ schema.IsSchemaValidated = true ++ } ++ ++ return err + } + + // fixYangRegexp takes a pattern regular expression from a YANG module and +diff -ruN ygot-dir-orig/ygot/ytypes/unmarshal.go ygot-dir/ygot/ytypes/unmarshal.go +--- ygot-dir-orig/ygot/ytypes/unmarshal.go 2019-10-24 12:30:07.753024000 -0700 ++++ ygot-dir/ygot/ytypes/unmarshal.go 2019-10-24 12:31:26.710027000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -73,7 +76,10 @@ + if schema == nil { + return fmt.Errorf("nil schema for parent type %T, value %v (%T)", parent, value, value) + } +- util.DbgPrint("Unmarshal value %v, type %T, into parent type %T, schema name %s", util.ValueStrDebug(value), value, parent, schema.Name) ++ ++ if (util.IsDebugLibraryEnabled()) { ++ util.DbgPrint("Unmarshal value %v, type %T, into parent type %T, schema name %s", util.ValueStrDebug(value), value, parent, schema.Name) ++ } + + if enc == GNMIEncoding && !(schema.IsLeaf() || schema.IsLeafList()) { + return errors.New("unmarshalling a non leaf node isn't supported in GNMIEncoding mode") +diff -ruN ygot-dir-orig/ygot/ytypes/util_schema.go ygot-dir/ygot/ytypes/util_schema.go +--- ygot-dir-orig/ygot/ytypes/util_schema.go 2019-10-24 12:30:07.763728000 -0700 ++++ ygot-dir/ygot/ytypes/util_schema.go 2019-10-24 12:31:26.715104000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -23,6 +26,8 @@ + "github.com/openconfig/ygot/util" + ) + ++var pathToSchemaCache map[reflect.StructTag][]string = make(map[reflect.StructTag][]string) ++ + // validateLengthSchema validates whether the given schema has a valid length + // specification. + func validateLengthSchema(schema *yang.Entry) error { +@@ -137,8 +142,16 @@ + // if the struct tag is invalid, or nil if tag is valid but the schema is not + // found in the tree at the specified path. + func childSchema(schema *yang.Entry, f reflect.StructField) (*yang.Entry, error) { +- pathTag, _ := f.Tag.Lookup("path") +- util.DbgSchema("childSchema for schema %s, field %s, tag %s\n", schema.Name, f.Name, pathTag) ++ if (schema.ChildSchemaCache == nil) { ++ schema.ChildSchemaCache = make(map[reflect.StructTag]*yang.Entry) ++ } else if cschema, ok := schema.ChildSchemaCache[f.Tag]; ok { ++ return cschema, nil ++ } ++ ++ if util.IsDebugSchemaEnabled() { ++ pathTag, _ := f.Tag.Lookup("path") ++ util.DbgSchema("childSchema for schema %s, field %s, tag %s\n", schema.Name, f.Name, pathTag) ++ } + p, err := pathToSchema(f) + if err != nil { + return nil, err +@@ -168,6 +181,7 @@ + } + if foundSchema { + util.DbgSchema(" - found\n") ++ schema.ChildSchemaCache[f.Tag] = childSchema + return childSchema, nil + } + util.DbgSchema(" - not found\n") +@@ -183,21 +197,25 @@ + // path element i.e. choice1/case1/leaf1 path in the schema will have + // struct tag `path:"leaf1"`. This implies that only paths with length + // 1 are eligible for this matching. ++ schema.ChildSchemaCache[f.Tag] = nil + return nil, nil + } + entries := util.FindFirstNonChoiceOrCase(schema) +- +- util.DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) ++ if util.IsDebugSchemaEnabled() { ++ util.DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) ++ } + for name, entry := range entries { + util.DbgSchema("%s ? ", name) + + if util.StripModulePrefix(name) == p[0] { + util.DbgSchema(" - match\n") ++ schema.ChildSchemaCache[f.Tag] = entry + return entry, nil + } + } + + util.DbgSchema(" - no matches\n") ++ schema.ChildSchemaCache[f.Tag] = nil + return nil, nil + } + +@@ -239,25 +257,32 @@ + // leafref. In the latter case, this function returns {"config", "a"}, and the + // schema *yang.Entry for the field is given by schema.Dir["config"].Dir["a"]. + func pathToSchema(f reflect.StructField) ([]string, error) { +- pathAnnotation, ok := f.Tag.Lookup("path") +- if !ok { +- return nil, fmt.Errorf("field %s did not specify a path", f.Name) +- } +- +- paths := strings.Split(pathAnnotation, "|") +- if len(paths) == 1 { +- pathAnnotation = strings.TrimPrefix(pathAnnotation, "/") +- return strings.Split(pathAnnotation, "/"), nil +- } +- for _, pv := range paths { +- pv = strings.TrimPrefix(pv, "/") +- pe := strings.Split(pv, "/") +- if len(pe) > 1 { ++ if pe, ok := pathToSchemaCache[f.Tag]; ok { ++ return pe, nil ++ } else { ++ pathAnnotation, ok := f.Tag.Lookup("path") ++ if !ok { ++ return nil, fmt.Errorf("field %s did not specify a path", f.Name) ++ } ++ ++ paths := strings.Split(pathAnnotation, "|") ++ if len(paths) == 1 { ++ pathAnnotation = strings.TrimPrefix(pathAnnotation, "/") ++ pe := strings.Split(pathAnnotation, "/") ++ pathToSchemaCache[f.Tag] = pe + return pe, nil + } ++ for _, pv := range paths { ++ pv = strings.TrimPrefix(pv, "/") ++ pe := strings.Split(pv, "/") ++ if len(pe) > 1 { ++ pathToSchemaCache[f.Tag] = pe ++ return pe, nil ++ } ++ } ++ ++ return nil, fmt.Errorf("field %s had path tag %s with |, but no elements of form a/b", f.Name, pathAnnotation) + } +- +- return nil, fmt.Errorf("field %s had path tag %s with |, but no elements of form a/b", f.Name, pathAnnotation) + } + + // directDescendantSchema returns the direct descendant schema for the struct +diff -ruN ygot-dir-orig/ygot/ytypes/validate.go ygot-dir/ygot/ytypes/validate.go +--- ygot-dir-orig/ygot/ytypes/validate.go 2019-10-24 12:30:07.778829000 -0700 ++++ ygot-dir/ygot/ytypes/validate.go 2019-10-24 12:31:26.719650000 -0700 +@@ -12,6 +12,9 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++// This file is changed by Broadcom. ++// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. ++ + package ytypes + + import ( +@@ -74,7 +77,7 @@ + errs = ValidateLeafRefData(schema, value, leafrefOpt) + } + +- util.DbgPrint("Validate with value %v, type %T, schema name %s", util.ValueStr(value), value, schema.Name) ++ util.DbgPrint("Validate with value %v, type %T, schema name %s", util.ValueStrDebug(value), value, schema.Name) + + switch { + case schema.IsLeaf(): diff --git a/tools/pyang/pyang_plugins/yin_cvl.py b/tools/pyang/pyang_plugins/yin_cvl.py new file mode 100644 index 000000000..71689003b --- /dev/null +++ b/tools/pyang/pyang_plugins/yin_cvl.py @@ -0,0 +1,179 @@ +################################################################################ +# # +# Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ +"""CVL YIN output plugin""" + +from xml.sax.saxutils import quoteattr +from xml.sax.saxutils import escape + +import optparse +import re + +from pyang import plugin +from pyang import util +from pyang import grammar +from pyang import syntax +from pyang import statements + +new_line ='' #replace with '\n' for adding new line +indent_space = '' #replace with ' ' for indentation +ns_indent_space = '' #replace with ' ' for indentation +yin_namespace = "urn:ietf:params:xml:ns:yang:yin:1" +revision_added = False + +def pyang_plugin_init(): + plugin.register_plugin(YINPluginCVL()) + +class YINPluginCVL(plugin.PyangPlugin): + def add_output_format(self, fmts): + fmts['yin-cvl'] = self + def emit(self, ctx, modules, fd): + module = modules[0] + emit_yin(ctx, module, fd) + +def emit_yin(ctx, module, fd): + fd.write('' + new_line) + fd.write(('<%s name="%s"' + new_line) % (module.keyword, module.arg)) + fd.write(ns_indent_space * len(module.keyword) + ns_indent_space + ' xmlns="%s"' % yin_namespace) + + prefix = module.search_one('prefix') + if prefix is not None: + namespace = module.search_one('namespace') + fd.write('' + new_line) + fd.write(ns_indent_space * len(module.keyword)) + fd.write(ns_indent_space + ' xmlns:' + prefix.arg + '=' + + quoteattr(namespace.arg)) + else: + belongs_to = module.search_one('belongs-to') + if belongs_to is not None: + prefix = belongs_to.search_one('prefix') + if prefix is not None: + # read the parent module in order to find the namespace uri + res = ctx.read_module(belongs_to.arg, extra={'no_include':True}) + if res is not None: + namespace = res.search_one('namespace') + if namespace is None or namespace.arg is None: + pass + else: + # success - namespace found + fd.write('' + new_line) + fd.write(sonic-acl.yin * len(module.keyword)) + fd.write(sonic-acl.yin + ' xmlns:' + prefix.arg + '=' + + quoteattr(namespace.arg)) + + for imp in module.search('import'): + prefix = imp.search_one('prefix') + if prefix is not None: + rev = None + r = imp.search_one('revision-date') + if r is not None: + rev = r.arg + mod = statements.modulename_to_module(module, imp.arg, rev) + if mod is not None: + ns = mod.search_one('namespace') + if ns is not None: + fd.write('' + new_line) + fd.write(ns_indent_space * len(module.keyword)) + fd.write(ns_indent_space + ' xmlns:' + prefix.arg + '=' + + quoteattr(ns.arg)) + fd.write('>' + new_line) + + substmts = module.substmts + for s in substmts: + emit_stmt(ctx, module, s, fd, indent_space, indent_space) + fd.write(('' + new_line) % module.keyword) + +def emit_stmt(ctx, module, stmt, fd, indent, indentstep): + global revision_added + + if stmt.raw_keyword == "revision" and revision_added == False: + revision_added = True + elif stmt.raw_keyword == "revision" and revision_added == True: + #Only add the latest revision + return + + #Don't keep the following keywords as they are not used in CVL + # stmt.raw_keyword == "revision" or + if ((stmt.raw_keyword == "organization" or + stmt.raw_keyword == "contact" or + stmt.raw_keyword == "rpc" or + stmt.raw_keyword == "notification" or + stmt.raw_keyword == "description") or + (len(stmt.substmts) > 0 and stmt.substmts[0].raw_keyword == "config" and + stmt.substmts[0].arg == "false")): + return + + if util.is_prefixed(stmt.raw_keyword): + # this is an extension. need to find its definition + (prefix, identifier) = stmt.raw_keyword + tag = prefix + ':' + identifier + if stmt.i_extension is not None: + ext_arg = stmt.i_extension.search_one('argument') + if ext_arg is not None: + yin_element = ext_arg.search_one('yin-element') + if yin_element is not None and yin_element.arg == 'true': + argname = prefix + ':' + ext_arg.arg + argiselem = True + else: + # explicit false or no yin-element given + argname = ext_arg.arg + argiselem = False + else: + argiselem = False + argname = None + else: + argiselem = False + argname = None + else: + (argname, argiselem) = syntax.yin_map[stmt.raw_keyword] + tag = stmt.raw_keyword + if argiselem == False or argname is None: + if argname is None: + attr = '' + else: + attr = ' ' + argname + '=' + quoteattr(stmt.arg) + if len(stmt.substmts) == 0: + fd.write(indent + '<' + tag + attr + '/>' + new_line) + else: + fd.write(indent + '<' + tag + attr + '>' + new_line) + for s in stmt.substmts: + emit_stmt(ctx, module, s, fd, indent + indentstep, + indentstep) + fd.write(indent + '' + new_line) + else: + fd.write(indent + '<' + tag + '>' + new_line) + fd.write(indent + indentstep + '<' + argname + '>' + \ + escape(stmt.arg) + \ + '' + new_line) + substmts = stmt.substmts + + for s in substmts: + emit_stmt(ctx, module, s, fd, indent + indentstep, indentstep) + + fd.write(indent + '' + new_line) + +def fmt_text(indent, data): + res = [] + for line in re.split("(\n)", escape(data)): + if line == '': + continue + if line == '' + new_line: + res.extend(line) + else: + res.extend(indent + line) + return ''.join(res) diff --git a/tools/xfmr/annotate.sh b/tools/xfmr/annotate.sh new file mode 100755 index 000000000..ede800252 --- /dev/null +++ b/tools/xfmr/annotate.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +################################################################################ +# # +# Copyright 2020 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # +# its subsidiaries. # +# # +# 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. # +# # +################################################################################ + +set -e + +[[ -z ${TOPDIR} ]] && TOPDIR=$(realpath $(dirname ${BASH_SOURCE[0]})/../..) +[[ -z ${MAKE} ]] && MAKE=make + +YANGDIR=${TOPDIR}/models/yang + +if [ -z $1 ]; then + echo "usage: $0 YANG_FILE_NAME..." + exit -1 +fi + +# Download, patch and compile goyang +${MAKE} -s -C ${TOPDIR} annotgen + +# Run goyang to generate annotation file for the specified yang file. +# Annotation output is dumped on stdout. +${TOPDIR}/build/bin/goyang --format=annotate --path=${YANGDIR} "$@" diff --git a/translib/Makefile b/translib/Makefile new file mode 100644 index 000000000..4acbd3140 --- /dev/null +++ b/translib/Makefile @@ -0,0 +1,53 @@ +####################################################################### +# +# Copyright 2019 Broadcom. All rights reserved. +# The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +# +####################################################################### + +TOPDIR ?= .. +BUILD_DIR ?= $(TOPDIR)/build + +GO ?= go +export GO + +TRANSLIB_PKG = $(BUILD_DIR)/pkg/linux_amd64/translib.a + +TRANSLIB_MAIN_SRCS = $(shell find . -name '*.go' | grep -v '_test.go' | grep -v '/test/') +TRANSLIB_TEST_SRCS = $(shell find . -maxdepth 1 -name '*_test.go') +TRANSL_DB_ALL_SRCS = $(shell find db/ -name '*.go' | grep -v '/test/') + +TRANSLIB_TEST_DIR = $(BUILD_DIR)/tests/translib +TRANSLIB_TEST_BIN = $(TRANSLIB_TEST_DIR)/translib.test +TRANSL_DB_TEST_BIN = $(TRANSLIB_TEST_DIR)/db.test + +YANG_FILES = $(shell find $(TOPDIR)/models/yang -name '*.yang') +YGOT_BINDS = ocbinds/ocbinds.go + +DEFAULT_TARGETS = $(YGOT_BINDS) +ifeq ($(NO_TEST_BINS),) +DEFAULT_TARGETS += $(TRANSLIB_TEST_BIN) $(TRANSL_DB_TEST_BIN) +endif + +default: $(DEFAULT_TARGETS) + +all: $(DEFAULT_TARGETS) $(TRANSLIB_PKG) + +$(TRANSLIB_PKG): $(TRANSLIB_MAIN_SRCS) $(YGOT_BINDS) + $(GO) build -mod=vendor -gcflags="all=-N -l" -v -o $@ ../translib + +$(TRANSLIB_TEST_BIN): $(TRANSLIB_MAIN_SRCS) $(TRANSLIB_TEST_SRCS) $(YGOT_BINDS) + $(GO) test -mod=vendor -cover -coverpkg=../translib,../translib/tlerr -c ../translib -o $@ + +$(TRANSL_DB_TEST_BIN) : $(TRANSL_DB_ALL_SRCS) + $(GO) test -mod=vendor -cover -c ../translib/db -o $@ + +$(YGOT_BINDS): $(YANG_FILES) + $(RM) $@ + cd ocbinds && $(GO) generate + +clean: + $(RM) $(YGOT_BINDS) + $(RM) $(TRANSLIB_PKG) + $(RM) -r $(TRANSLIB_TEST_DIR) + diff --git a/translib/acl_app.go b/translib/acl_app.go new file mode 100644 index 000000000..628d4ea27 --- /dev/null +++ b/translib/acl_app.go @@ -0,0 +1,1712 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "bytes" + "fmt" + "reflect" + "strconv" + "strings" + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" + + log "github.com/golang/glog" + "github.com/openconfig/ygot/util" + "github.com/openconfig/ygot/ygot" +) + +const ( + TABLE_SEPARATOR = "|" + KEY_SEPARATOR = "|" + ACL_TABLE = "ACL_TABLE" + RULE_TABLE = "ACL_RULE" + ACL_TYPE = "type" + ACL_DESCRIPTION = "policy_desc" + SONIC_ACL_TYPE_L2 = "L2" + SONIC_ACL_TYPE_IPV4 = "L3" + SONIC_ACL_TYPE_IPV6 = "L3V6" + OPENCONFIG_ACL_TYPE_IPV4 = "ACL_IPV4" + OPENCONFIG_ACL_TYPE_IPV6 = "ACL_IPV6" + OPENCONFIG_ACL_TYPE_L2 = "ACL_L2" + OC_ACL_APP_MODULE_NAME = "/openconfig-acl:acl" + OC_ACL_YANG_PATH_PREFIX = "/device/acl" + + MIN_PRIORITY = 1 + MAX_PRIORITY = 65536 +) + +var IP_PROTOCOL_MAP = map[ocbinds.E_OpenconfigPacketMatchTypes_IP_PROTOCOL]uint8{ + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_ICMP: 1, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_IGMP: 2, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_TCP: 6, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_UDP: 17, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_RSVP: 46, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_GRE: 47, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_AUTH: 51, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_PIM: 103, + ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_L2TP: 115, +} + +var ETHERTYPE_MAP = map[ocbinds.E_OpenconfigPacketMatchTypes_ETHERTYPE]uint32{ + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_LLDP: 0x88CC, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_VLAN: 0x8100, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_ROCE: 0x8915, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_ARP: 0x0806, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV4: 0x0800, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV6: 0x86DD, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_MPLS: 0x8847, +} + +type AclApp struct { + pathInfo *PathInfo + ygotRoot *ygot.GoStruct + ygotTarget *interface{} + + aclTs *db.TableSpec + ruleTs *db.TableSpec + + aclTableMap map[string]db.Value + ruleTableMap map[string]map[string]db.Value +} + +func init() { + + err := register("/openconfig-acl:acl", + &appInfo{appType: reflect.TypeOf(AclApp{}), + ygotRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + isNative: false, + tablesToWatch: []*db.TableSpec{&db.TableSpec{Name: ACL_TABLE}, &db.TableSpec{Name: RULE_TABLE}}}) + + if err != nil { + log.Fatal("Register ACL app module with App Interface failed with error=", err) + } + + err = addModel(&ModelData{Name: "openconfig-acl", + Org: "OpenConfig working group", + Ver: "1.0.2"}) + if err != nil { + log.Fatal("Adding model data to appinterface failed with error=", err) + } +} + +func (app *AclApp) initialize(data appData) { + log.Info("initialize:acl:path =", data.path) + pathInfo := NewPathInfo(data.path) + *app = AclApp{pathInfo: pathInfo, ygotRoot: data.ygotRoot, ygotTarget: data.ygotTarget} + + app.aclTs = &db.TableSpec{Name: ACL_TABLE} + app.ruleTs = &db.TableSpec{Name: RULE_TABLE} + + app.aclTableMap = make(map[string]db.Value) + app.ruleTableMap = make(map[string]map[string]db.Value) +} + +func (app *AclApp) getAppRootObject() *ocbinds.OpenconfigAcl_Acl { + deviceObj := (*app.ygotRoot).(*ocbinds.Device) + return deviceObj.Acl +} + +func (app *AclApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateCreate:acl:path =", app.pathInfo.Template) + + keys, err = app.translateCRUCommon(d, CREATE) + return keys, err +} + +func (app *AclApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateUpdate:acl:path =", app.pathInfo.Template) + + keys, err = app.translateCRUCommon(d, UPDATE) + return keys, err +} + +func (app *AclApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateReplace:acl:path =", app.pathInfo.Template) + + keys, err = app.translateCRUCommon(d, REPLACE) + return keys, err +} + +func (app *AclApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateDelete:acl:path =", app.pathInfo.Template) + + return keys, err +} + +func (app *AclApp) translateGet(dbs [db.MaxDB]*db.DB) error { + var err error + log.Info("translateGet:acl:path =", app.pathInfo.Template) + return err +} + +func (app *AclApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { + pathInfo := NewPathInfo(path) + notifInfo := notificationInfo{dbno: db.ConfigDB} + notSupported := tlerr.NotSupportedError{ + Format: "Subscribe not supported", Path: path} + + if isSubtreeRequest(pathInfo.Template, "/openconfig-acl:acl/acl-sets") { + // Subscribing to top level ACL record is not supported. It requires listening + // to 2 tables (ACL and ACL_RULE); TransLib does not support it yet + if pathInfo.HasSuffix("/acl-sets") || + pathInfo.HasSuffix("/acl-set") || + pathInfo.HasSuffix("/acl-set{}{}") { + log.Errorf("Subscribe not supported for top level ACL %s", pathInfo.Template) + return nil, nil, notSupported + } + + t, err := getAclTypeOCEnumFromName(pathInfo.Var("type")) + if err != nil { + return nil, nil, err + } + + aclkey := getAclKeyStrFromOCKey(pathInfo.Var("name"), t) + + if strings.Contains(pathInfo.Template, "/acl-entry{}") { + // Subscribe for one rule + rulekey := "RULE_" + pathInfo.Var("sequence-id") + notifInfo.table = db.TableSpec{Name: RULE_TABLE} + notifInfo.key = asKey(aclkey, rulekey) + notifInfo.needCache = !pathInfo.HasSuffix("/acl-entry{}") + + } else if pathInfo.HasSuffix("/acl-entries") || pathInfo.HasSuffix("/acl-entry") { + // Subscribe for all rules of an ACL + notifInfo.table = db.TableSpec{Name: RULE_TABLE} + notifInfo.key = asKey(aclkey, "*") + + } else { + // Subscibe for ACL fields only + notifInfo.table = db.TableSpec{Name: ACL_TABLE} + notifInfo.key = asKey(aclkey) + notifInfo.needCache = true + } + + } else if isSubtreeRequest(pathInfo.Template, "/openconfig-acl:acl/interfaces") { + // Right now interface binding config is maintained within ACL + // table itself. Multiple ACLs can be bound to one intf; one + // inname can occur in multiple ACL entries. So we cannot map + // interface binding xpaths to specific ACL table entry keys. + // For now subscribe for full ACL table!! + notifInfo.table = db.TableSpec{Name: ACL_TABLE} + notifInfo.key = asKey("*") + notifInfo.needCache = true + + } else { + log.Errorf("Unknown path %s", pathInfo.Template) + return nil, nil, notSupported + } + + return nil, ¬ifInfo, nil +} + +func (app *AclApp) processCreate(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + if err = app.processCommon(d, CREATE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + return resp, err +} + +func (app *AclApp) processUpdate(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + if err = app.processCommon(d, UPDATE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + return resp, err +} + +func (app *AclApp) processReplace(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + if err = app.processCommon(d, REPLACE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + return resp, err +} + +func (app *AclApp) processDelete(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + if err = app.processCommon(d, DELETE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + return resp, err +} + +func (app *AclApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { + var err error + var payload []byte + + configDb := dbs[db.ConfigDB] + err = app.processCommon(configDb, GET) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + payload, err = generateGetResponsePayload(app.pathInfo.Path, (*app.ygotRoot).(*ocbinds.Device), app.ygotTarget) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + return GetResponse{Payload: payload}, err +} + +func (app *AclApp) translateCRUCommon(d *db.DB, opcode int) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateCRUCommon:acl:path =", app.pathInfo.Template) + + app.convertOCAclsToInternal() + app.convertOCAclRulesToInternal(d) + app.convertOCAclBindingsToInternal() + + return keys, err +} + +func (app *AclApp) processCommon(d *db.DB, opcode int) error { + var err error + var topmostPath bool = false + acl := app.getAppRootObject() + + log.Infof("processCommon--Path Received: %s", app.pathInfo.Template) + targetType := reflect.TypeOf(*app.ygotTarget) + if !util.IsValueScalar(reflect.ValueOf(*app.ygotTarget)) && util.IsValuePtr(reflect.ValueOf(*app.ygotTarget)) { + log.Infof("processCommon: Target object is a <%s> of Type: %s", targetType.Kind().String(), targetType.Elem().Name()) + if targetType.Elem().Name() == "OpenconfigAcl_Acl" { + topmostPath = true + } + } + + targetUriPath, _ := getYangPathFromUri(app.pathInfo.Path) + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/acl-sets") { + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/acl-sets/acl-set{}{}") { + for aclSetKey, _ := range acl.AclSets.AclSet { + aclSet := acl.AclSets.AclSet[aclSetKey] + aclKey := getAclKeyStrFromOCKey(aclSetKey.Name, aclSetKey.Type) + + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/acl-sets/acl-set{}{}/acl-entries/acl-entry{}") { + // Subtree of one Rule + for seqId, _ := range aclSet.AclEntries.AclEntry { + ruleKey := "RULE_" + strconv.Itoa(int(seqId)) + entrySet := aclSet.AclEntries.AclEntry[seqId] + + ruleNodeYangPath := getYangPathFromYgotStruct(entrySet, OC_ACL_YANG_PATH_PREFIX, OC_ACL_APP_MODULE_NAME) + isRuleNodeSubtree := len(targetUriPath) > len(ruleNodeYangPath) + switch opcode { + case CREATE: + if isRuleNodeSubtree { + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, false) + } else if *app.ygotTarget == entrySet { + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, true) + } else { + log.Errorf("processCommon: Given CREATE path %s not handled", targetUriPath) + } + case REPLACE: + err = d.SetEntry(app.ruleTs, db.Key{Comp: []string{aclKey, ruleKey}}, app.ruleTableMap[aclKey][ruleKey]) + case UPDATE: + err = d.ModEntry(app.ruleTs, db.Key{Comp: []string{aclKey, ruleKey}}, app.ruleTableMap[aclKey][ruleKey]) + case DELETE: + if *app.ygotTarget == entrySet { + err = d.DeleteEntry(app.ruleTs, db.Key{Comp: []string{aclKey, ruleKey}}) + } else if isRuleNodeSubtree { + err = app.handleRuleFieldsDeletion(d, aclKey, ruleKey) + if err != nil { + return err + } + //err = d.SetEntry(app.ruleTs, db.Key{Comp: []string{aclKey, ruleKey}}, app.ruleTableMap[aclKey][ruleKey]) + } else { + log.Errorf("processCommon: Given DELETE path %s not handled", targetUriPath) + } + case GET: + err = app.convertDBAclRulesToInternal(d, aclKey, int64(seqId), db.Key{}) + ygot.BuildEmptyTree(entrySet) + app.convertInternalToOCAclRule(aclKey, aclSetKey.Type, int64(seqId), nil, entrySet) + } + } + } else { + isAclEntriesSubtree := isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/acl-sets/acl-set{}{}/acl-entries") + switch opcode { + case CREATE: + if *app.ygotTarget == aclSet { + err = app.setAclDataInConfigDb(d, app.aclTableMap, true) + if err != nil { + return err + } + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, true) + } else if isAclEntriesSubtree { + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, true) + } else { + err = d.SetEntry(app.aclTs, db.Key{Comp: []string{aclKey}}, app.aclTableMap[aclKey]) + } + case REPLACE: + if *app.ygotTarget == aclSet || isAclEntriesSubtree { + err = d.DeleteKeys(app.ruleTs, db.Key{Comp: []string{aclKey + TABLE_SEPARATOR + "RULE_*"}}) + if err != nil { + return err + } + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, true) + if err != nil { + return err + } + } + if !isAclEntriesSubtree { + err = d.ModEntry(app.aclTs, db.Key{Comp: []string{aclKey}}, app.aclTableMap[aclKey]) + } + case UPDATE: + if !isAclEntriesSubtree { + err = app.setAclDataInConfigDb(d, app.aclTableMap, false) + //err = d.ModEntry(app.aclTs, db.Key{Comp: []string{aclKey}}, app.aclTableMap[aclKey]) + if err != nil { + return err + } + } + if *app.ygotTarget == aclSet || isAclEntriesSubtree { + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, false) + } + case DELETE: + if *app.ygotTarget == aclSet { + err = d.DeleteKeys(app.ruleTs, db.Key{Comp: []string{aclKey + TABLE_SEPARATOR + "*"}}) + if err != nil { + return err + } + err = d.DeleteEntry(app.aclTs, db.Key{Comp: []string{aclKey}}) + } else if isAclEntriesSubtree { + err = d.DeleteKeys(app.ruleTs, db.Key{Comp: []string{aclKey + TABLE_SEPARATOR + "RULE_*"}}) + } else { + nodeInfo, err := getTargetNodeYangSchema(app.pathInfo.Path, (*app.ygotRoot).(*ocbinds.Device)) + if err != nil { + return err + } + if nodeInfo != nil && nodeInfo.IsLeaf() && nodeInfo.Name == "description" { + err = d.DeleteEntryFields(app.aclTs, asKey(aclKey), createEmptyDbValue(ACL_DESCRIPTION)) + } + //err = d.SetEntry(app.aclTs, db.Key{Comp: []string{aclKey}}, app.aclTableMap[aclKey]) + } + case GET: + err = app.convertDBAclToInternal(d, db.Key{Comp: []string{aclKey}}) + if err != nil { + return err + } + ygot.BuildEmptyTree(aclSet) + app.convertInternalToOCAcl(aclKey, acl.AclSets, aclSet) + } + } + } + } else { + // All Acls and their rules + err = app.processCommonToplevelPath(d, acl, opcode, false) + } + } else if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces") { + switch opcode { + case CREATE, REPLACE, UPDATE: + err = app.setAclBindDataInConfigDb(d, app.aclTableMap, opcode) + case DELETE: + err = app.handleBindingsDeletion(d) + case GET: + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}") { + for intfId := range acl.Interfaces.Interface { + intfData := acl.Interfaces.Interface[intfId] + ygot.BuildEmptyTree(intfData) + if isSubtreeRequest(targetUriPath, "/openconfig-acl:acl/interfaces/interface/ingress-acl-sets") { + err = app.getAclBindingInfoForInterfaceData(d, intfData, intfId, "INGRESS") + } else if isSubtreeRequest(targetUriPath, "/openconfig-acl:acl/interfaces/interface/egress-acl-sets") { + err = app.getAclBindingInfoForInterfaceData(d, intfData, intfId, "EGRESS") + } else { + // Direction unknown. Check ACL Table for binding information. + err = app.getAclBindingInfoForInterfaceData(d, intfData, intfId, "INGRESS") + if err != nil { + return err + } + err = app.getAclBindingInfoForInterfaceData(d, intfData, intfId, "EGRESS") + } + } + } else { + err = app.getAllBindingsInfo(d) + } + } + } else { + err = app.processCommonToplevelPath(d, acl, opcode, true) + } + + if !topmostPath && !isSubtreeRequest(targetUriPath, "/openconfig-acl:acl/acl-sets") && !isSubtreeRequest(targetUriPath, "/openconfig-acl:acl/interfaces") { + err = tlerr.NotSupported("URL %s is not supported", app.pathInfo.Template) + } + + return err +} + +func (app *AclApp) processCommonToplevelPath(d *db.DB, acl *ocbinds.OpenconfigAcl_Acl, opcode int, isTopmostPath bool) error { + var err error + switch opcode { + case CREATE: + err = app.setAclDataInConfigDb(d, app.aclTableMap, true) + if err != nil { + return err + } + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, true) + case REPLACE: + err = d.DeleteTable(app.aclTs) + if err != nil { + return err + } + err = d.DeleteTable(app.ruleTs) + if err != nil { + return err + } + err = app.setAclDataInConfigDb(d, app.aclTableMap, true) + if err != nil { + return err + } + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, true) + case UPDATE: + err = app.setAclDataInConfigDb(d, app.aclTableMap, false) + if err != nil { + return err + } + err = app.setAclRuleDataInConfigDb(d, app.ruleTableMap, false) + case DELETE: + err = d.DeleteTable(app.ruleTs) + if err != nil { + return err + } + err = d.DeleteTable(app.aclTs) + case GET: + ygot.BuildEmptyTree(acl) + err = app.convertDBAclToInternal(d, db.Key{}) + if err != nil { + return err + } + app.convertInternalToOCAcl("", acl.AclSets, nil) + if isTopmostPath { + err = app.getAllBindingsInfo(d) + } + } + return err +} + +/*********** These are Translation Helper Function ***********/ +func (app *AclApp) convertDBAclRulesToInternal(dbCl *db.DB, aclName string, seqId int64, ruleKey db.Key) error { + var err error + if seqId != -1 { + ruleKey.Comp = []string{aclName, "RULE_" + strconv.FormatInt(int64(seqId), 10)} + } + if ruleKey.Len() > 1 { + ruleName := ruleKey.Get(1) + if ruleName != "DEFAULT_RULE" { + ruleData, err := dbCl.GetEntry(app.ruleTs, ruleKey) + if err != nil { + return err + } + if app.ruleTableMap[aclName] == nil { + app.ruleTableMap[aclName] = make(map[string]db.Value) + } + app.ruleTableMap[aclName][ruleName] = ruleData + } + } else { + ruleKeys, err := dbCl.GetKeys(app.ruleTs) + if err != nil { + return err + } + for i, _ := range ruleKeys { + if aclName == ruleKeys[i].Get(0) { + app.convertDBAclRulesToInternal(dbCl, aclName, -1, ruleKeys[i]) + } + } + } + return err +} + +func (app *AclApp) convertDBAclToInternal(dbCl *db.DB, aclkey db.Key) error { + var err error + if aclkey.Len() > 0 { + // Get one particular ACL + entry, err := dbCl.GetEntry(app.aclTs, aclkey) + if err != nil { + return err + } + if entry.IsPopulated() { + app.aclTableMap[aclkey.Get(0)] = entry + app.ruleTableMap[aclkey.Get(0)] = make(map[string]db.Value) + err = app.convertDBAclRulesToInternal(dbCl, aclkey.Get(0), -1, db.Key{}) + if err != nil { + return err + } + } else { + return tlerr.NotFound("Acl %s is not configured", aclkey.Get(0)) + } + } else { + // Get all ACLs + tbl, err := dbCl.GetTable(app.aclTs) + if err != nil { + return err + } + keys, _ := tbl.GetKeys() + for i, _ := range keys { + app.convertDBAclToInternal(dbCl, keys[i]) + } + } + return err +} + +func (app *AclApp) convertInternalToOCAcl(aclName string, aclSets *ocbinds.OpenconfigAcl_Acl_AclSets, aclSet *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet) { + if len(aclName) > 0 { + aclData := app.aclTableMap[aclName] + if aclSet != nil { + aclSet.Config.Name = aclSet.Name + aclSet.Config.Type = aclSet.Type + aclSet.State.Name = aclSet.Name + aclSet.State.Type = aclSet.Type + + for k := range aclData.Field { + if ACL_DESCRIPTION == k { + descr := aclData.Get(k) + aclSet.Config.Description = &descr + aclSet.State.Description = &descr + } else if "ports@" == k { + continue + } + } + + app.convertInternalToOCAclRule(aclName, aclSet.Type, -1, aclSet, nil) + } + } else { + for acln := range app.aclTableMap { + acldata := app.aclTableMap[acln] + var aclNameStr string + var aclType ocbinds.E_OpenconfigAcl_ACL_TYPE + if acldata.Get(ACL_TYPE) == SONIC_ACL_TYPE_IPV4 { + aclNameStr = strings.Replace(acln, "_"+OPENCONFIG_ACL_TYPE_IPV4, "", 1) + aclType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 + } else if acldata.Get(ACL_TYPE) == SONIC_ACL_TYPE_IPV6 { + aclNameStr = strings.Replace(acln, "_"+OPENCONFIG_ACL_TYPE_IPV6, "", 1) + aclType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 + } else if acldata.Get(ACL_TYPE) == SONIC_ACL_TYPE_L2 { + aclNameStr = strings.Replace(acln, "_"+OPENCONFIG_ACL_TYPE_L2, "", 1) + aclType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 + } + aclSetPtr, aclErr := aclSets.NewAclSet(aclNameStr, aclType) + if aclErr != nil { + fmt.Println("Error handling: ", aclErr) + } + ygot.BuildEmptyTree(aclSetPtr) + app.convertInternalToOCAcl(acln, nil, aclSetPtr) + } + } +} + +func (app *AclApp) convertInternalToOCAclRule(aclName string, aclType ocbinds.E_OpenconfigAcl_ACL_TYPE, seqId int64, aclSet *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet, entrySet *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if seqId != -1 { + ruleName := "RULE_" + strconv.FormatInt(int64(seqId), 10) + app.convertInternalToOCAclRuleProperties(app.ruleTableMap[aclName][ruleName], aclType, nil, entrySet) + } else { + for ruleName := range app.ruleTableMap[aclName] { + app.convertInternalToOCAclRuleProperties(app.ruleTableMap[aclName][ruleName], aclType, aclSet, nil) + } + } +} + +func (app *AclApp) convertInternalToOCAclRuleProperties(ruleData db.Value, aclType ocbinds.E_OpenconfigAcl_ACL_TYPE, aclSet *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet, entrySet *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + priority, _ := strconv.ParseInt(ruleData.Get("PRIORITY"), 10, 32) + seqId := uint32(MAX_PRIORITY - priority) + //ruleDescr := ruleData.Get("RULE_DESCRIPTION") + + if entrySet == nil { + if aclSet != nil { + entrySet_, _ := aclSet.AclEntries.NewAclEntry(seqId) + entrySet = entrySet_ + ygot.BuildEmptyTree(entrySet) + } + } + + entrySet.Config.SequenceId = &seqId + //entrySet.Config.Description = &ruleDescr + entrySet.State.SequenceId = &seqId + //entrySet.State.Description = &ruleDescr + + var num uint64 + num = 0 + entrySet.State.MatchedOctets = &num + entrySet.State.MatchedPackets = &num + + ygot.BuildEmptyTree(entrySet.Transport) + ygot.BuildEmptyTree(entrySet.Actions) + + for ruleKey := range ruleData.Field { + if "L4_SRC_PORT" == ruleKey || "L4_SRC_PORT_RANGE" == ruleKey { + port := ruleData.Get(ruleKey) + srcPort := getTransportSrcDestPorts(port, "src") + entrySet.Transport.Config.SourcePort, _ = entrySet.Transport.Config.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union(srcPort) + entrySet.Transport.State.SourcePort, _ = entrySet.Transport.State.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_State_SourcePort_Union(srcPort) + } else if "L4_DST_PORT" == ruleKey || "L4_DST_PORT_RANGE" == ruleKey { + port := ruleData.Get(ruleKey) + destPort := getTransportSrcDestPorts(port, "dest") + entrySet.Transport.Config.DestinationPort, _ = entrySet.Transport.Config.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union(destPort) + entrySet.Transport.State.DestinationPort, _ = entrySet.Transport.State.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_State_DestinationPort_Union(destPort) + } else if "TCP_FLAGS" == ruleKey { + tcpFlags := ruleData.Get(ruleKey) + entrySet.Transport.Config.TcpFlags = getTransportConfigTcpFlags(tcpFlags) + entrySet.Transport.State.TcpFlags = getTransportConfigTcpFlags(tcpFlags) + } else if "PACKET_ACTION" == ruleKey { + if "FORWARD" == ruleData.Get(ruleKey) { + entrySet.Actions.Config.ForwardingAction = ocbinds.OpenconfigAcl_FORWARDING_ACTION_ACCEPT + entrySet.Actions.State.ForwardingAction = ocbinds.OpenconfigAcl_FORWARDING_ACTION_ACCEPT + } else { + entrySet.Actions.Config.ForwardingAction = ocbinds.OpenconfigAcl_FORWARDING_ACTION_DROP + entrySet.Actions.State.ForwardingAction = ocbinds.OpenconfigAcl_FORWARDING_ACTION_DROP + } + } + } + + if aclType == ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 { + ygot.BuildEmptyTree(entrySet.Ipv4) + for ruleKey := range ruleData.Field { + if "IP_PROTOCOL" == ruleKey { + ipProto, _ := strconv.ParseInt(ruleData.Get(ruleKey), 10, 64) + protocolVal := getIpProtocol(ipProto) + entrySet.Ipv4.Config.Protocol, _ = entrySet.Ipv4.Config.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union(protocolVal) + entrySet.Ipv4.State.Protocol, _ = entrySet.Ipv4.State.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_State_Protocol_Union(protocolVal) + } else if "DSCP" == ruleKey { + var dscp uint8 + dscpData, _ := strconv.ParseInt(ruleData.Get(ruleKey), 10, 64) + dscp = uint8(dscpData) + entrySet.Ipv4.Config.Dscp = &dscp + entrySet.Ipv4.State.Dscp = &dscp + } else if "SRC_IP" == ruleKey { + addr := ruleData.Get(ruleKey) + entrySet.Ipv4.Config.SourceAddress = &addr + entrySet.Ipv4.State.SourceAddress = &addr + } else if "DST_IP" == ruleKey { + addr := ruleData.Get(ruleKey) + entrySet.Ipv4.Config.DestinationAddress = &addr + entrySet.Ipv4.State.DestinationAddress = &addr + } + } + } else if aclType == ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 { + ygot.BuildEmptyTree(entrySet.Ipv6) + for ruleKey := range ruleData.Field { + if "IP_PROTOCOL" == ruleKey { + ipProto, _ := strconv.ParseInt(ruleData.Get(ruleKey), 10, 64) + protocolVal := getIpProtocol(ipProto) + entrySet.Ipv6.Config.Protocol, _ = entrySet.Ipv6.Config.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_Config_Protocol_Union(protocolVal) + entrySet.Ipv6.State.Protocol, _ = entrySet.Ipv6.State.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_State_Protocol_Union(protocolVal) + } else if "DSCP" == ruleKey { + var dscp uint8 + dscpData, _ := strconv.ParseInt(ruleData.Get(ruleKey), 10, 64) + dscp = uint8(dscpData) + entrySet.Ipv6.Config.Dscp = &dscp + entrySet.Ipv6.State.Dscp = &dscp + } else if "SRC_IPV6" == ruleKey { + addr := ruleData.Get(ruleKey) + entrySet.Ipv6.Config.SourceAddress = &addr + entrySet.Ipv6.State.SourceAddress = &addr + } else if "DST_IPV6" == ruleKey { + addr := ruleData.Get(ruleKey) + entrySet.Ipv6.Config.DestinationAddress = &addr + entrySet.Ipv6.State.DestinationAddress = &addr + } + } + } else if aclType == ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 { + ygot.BuildEmptyTree(entrySet.L2) + for ruleKey := range ruleData.Field { + if "ETHER_TYPE" == ruleKey { + ethType, _ := strconv.ParseUint(strings.Replace(ruleData.Get(ruleKey), "0x", "", -1), 16, 32) + ethertype := getL2EtherType(ethType) + entrySet.L2.Config.Ethertype, _ = entrySet.L2.Config.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union(ethertype) + entrySet.L2.State.Ethertype, _ = entrySet.L2.State.To_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_State_Ethertype_Union(ethertype) + } + } + } +} + +func convertInternalToOCAclRuleBinding(d *db.DB, priority uint32, seqId int64, direction string, aclSet ygot.GoStruct, entrySet ygot.GoStruct) { + if seqId == -1 { + seqId = int64(MAX_PRIORITY - priority) + } + + var num uint64 + num = 0 + var ruleId uint32 = uint32(seqId) + + if direction == "INGRESS" { + var ingressEntrySet *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_AclEntries_AclEntry + var ok bool + if entrySet == nil { + ingressAclSet := aclSet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet) + if ingressEntrySet, ok = ingressAclSet.AclEntries.AclEntry[ruleId]; !ok { + ingressEntrySet, _ = ingressAclSet.AclEntries.NewAclEntry(ruleId) + } + } else { + ingressEntrySet = entrySet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_AclEntries_AclEntry) + } + if ingressEntrySet != nil { + ygot.BuildEmptyTree(ingressEntrySet) + ingressEntrySet.State.SequenceId = &ruleId + ingressEntrySet.State.MatchedPackets = &num + ingressEntrySet.State.MatchedOctets = &num + } + } else if direction == "EGRESS" { + var egressEntrySet *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_AclEntries_AclEntry + var ok bool + if entrySet == nil { + egressAclSet := aclSet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet) + if egressEntrySet, ok = egressAclSet.AclEntries.AclEntry[ruleId]; !ok { + egressEntrySet, _ = egressAclSet.AclEntries.NewAclEntry(ruleId) + } + } else { + egressEntrySet = entrySet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_AclEntries_AclEntry) + } + if egressEntrySet != nil { + ygot.BuildEmptyTree(egressEntrySet) + egressEntrySet.State.SequenceId = &ruleId + egressEntrySet.State.MatchedPackets = &num + egressEntrySet.State.MatchedOctets = &num + } + } +} + +func (app *AclApp) convertInternalToOCAclBinding(d *db.DB, aclName string, intfId string, direction string, intfAclSet ygot.GoStruct) error { + var err error + if _, ok := app.aclTableMap[aclName]; !ok { + aclEntry, err1 := d.GetEntry(app.aclTs, db.Key{Comp: []string{aclName}}) + if err1 != nil { + return err1 + } + if !contains(aclEntry.GetList("ports"), intfId) { + return tlerr.InvalidArgs("Acl %s not binded with %s", aclName, intfId) + } + } + + if _, ok := app.ruleTableMap[aclName]; !ok { + ruleKeys, _ := d.GetKeys(app.ruleTs) + for i, _ := range ruleKeys { + rulekey := ruleKeys[i] + // Rulekey has two keys, first aclkey and second rulename + if rulekey.Get(0) == aclName && rulekey.Get(1) != "DEFAULT_RULE" { + seqId, _ := strconv.Atoi(strings.Replace(rulekey.Get(1), "RULE_", "", 1)) + convertInternalToOCAclRuleBinding(d, 0, int64(seqId), direction, intfAclSet, nil) + } + } + } else { + for ruleName := range app.ruleTableMap[aclName] { + if ruleName != "DEFAULT_RULE" { + seqId, _ := strconv.Atoi(strings.Replace(ruleName, "RULE_", "", 1)) + convertInternalToOCAclRuleBinding(d, 0, int64(seqId), direction, intfAclSet, nil) + } + } + } + + return err +} + +func (app *AclApp) getAllBindingsInfo(d *db.DB) error { + var err error + acl := app.getAppRootObject() + if len(app.aclTableMap) == 0 { + aclKeys, _ := d.GetKeys(app.aclTs) + for i, _ := range aclKeys { + aclEntry, _ := d.GetEntry(app.aclTs, aclKeys[i]) + app.aclTableMap[(aclKeys[i]).Get(0)] = aclEntry + } + } + var interfaces []string + for aclName := range app.aclTableMap { + aclData := app.aclTableMap[aclName] + if len(aclData.Get("ports@")) > 0 { + aclIntfs := aclData.GetList("ports") + for i, _ := range aclIntfs { + if !contains(interfaces, aclIntfs[i]) && aclIntfs[i] != "" { + interfaces = append(interfaces, aclIntfs[i]) + } + } + } + } + + for _, intfId := range interfaces { + var intfData *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface + intfData, ok := acl.Interfaces.Interface[intfId] + if !ok { + intfData, _ = acl.Interfaces.NewInterface(intfId) + } + ygot.BuildEmptyTree(intfData) + err = app.getAclBindingInfoForInterfaceData(d, intfData, intfId, "INGRESS") + err = app.getAclBindingInfoForInterfaceData(d, intfData, intfId, "EGRESS") + } + return err +} + +func (app *AclApp) getAclBindingInfoForInterfaceData(d *db.DB, intfData *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface, intfId string, direction string) error { + var err error + if intfData != nil { + intfData.Config.Id = intfData.Id + intfData.State.Id = intfData.Id + } + if direction == "INGRESS" { + if intfData.IngressAclSets != nil && len(intfData.IngressAclSets.IngressAclSet) > 0 { + for ingressAclSetKey, _ := range intfData.IngressAclSets.IngressAclSet { + aclName := strings.Replace(strings.Replace(ingressAclSetKey.SetName, " ", "_", -1), "-", "_", -1) + aclType := ingressAclSetKey.Type.ΛMap()["E_OpenconfigAcl_ACL_TYPE"][int64(ingressAclSetKey.Type)].Name + aclKey := aclName + "_" + aclType + + ingressAclSet := intfData.IngressAclSets.IngressAclSet[ingressAclSetKey] + if ingressAclSet != nil && ingressAclSet.AclEntries != nil && len(ingressAclSet.AclEntries.AclEntry) > 0 { + for seqId, _ := range ingressAclSet.AclEntries.AclEntry { + rulekey := "RULE_" + strconv.Itoa(int(seqId)) + entrySet := ingressAclSet.AclEntries.AclEntry[seqId] + _, err := d.GetEntry(app.ruleTs, db.Key{Comp: []string{aclKey, rulekey}}) + if err != nil { + return err + } + convertInternalToOCAclRuleBinding(d, 0, int64(seqId), direction, nil, entrySet) + } + } else { + ygot.BuildEmptyTree(ingressAclSet) + ingressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_Config{SetName: &aclName, Type: ingressAclSetKey.Type} + ingressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_State{SetName: &aclName, Type: ingressAclSetKey.Type} + err = app.convertInternalToOCAclBinding(d, aclKey, intfId, direction, ingressAclSet) + } + } + } else { + err = app.findAndGetAclBindingInfoForInterfaceData(d, intfId, direction, intfData) + } + } else if direction == "EGRESS" { + if intfData.EgressAclSets != nil && len(intfData.EgressAclSets.EgressAclSet) > 0 { + for egressAclSetKey, _ := range intfData.EgressAclSets.EgressAclSet { + aclName := strings.Replace(strings.Replace(egressAclSetKey.SetName, " ", "_", -1), "-", "_", -1) + aclType := egressAclSetKey.Type.ΛMap()["E_OpenconfigAcl_ACL_TYPE"][int64(egressAclSetKey.Type)].Name + aclKey := aclName + "_" + aclType + + egressAclSet := intfData.EgressAclSets.EgressAclSet[egressAclSetKey] + if egressAclSet != nil && egressAclSet.AclEntries != nil && len(egressAclSet.AclEntries.AclEntry) > 0 { + for seqId, _ := range egressAclSet.AclEntries.AclEntry { + rulekey := "RULE_" + strconv.Itoa(int(seqId)) + entrySet := egressAclSet.AclEntries.AclEntry[seqId] + _, err := d.GetEntry(app.ruleTs, db.Key{Comp: []string{aclKey, rulekey}}) + if err != nil { + return err + } + convertInternalToOCAclRuleBinding(d, 0, int64(seqId), direction, nil, entrySet) + } + } else { + ygot.BuildEmptyTree(egressAclSet) + egressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_Config{SetName: &aclName, Type: egressAclSetKey.Type} + egressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_State{SetName: &aclName, Type: egressAclSetKey.Type} + err = app.convertInternalToOCAclBinding(d, aclKey, intfId, direction, egressAclSet) + } + } + } else { + err = app.findAndGetAclBindingInfoForInterfaceData(d, intfId, direction, intfData) + } + } else { + log.Error("Unknown direction") + } + return err +} + +func (app *AclApp) findAndGetAclBindingInfoForInterfaceData(d *db.DB, intfId string, direction string, intfData *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface) error { + var err error + if len(app.aclTableMap) == 0 { + aclKeys, _ := d.GetKeys(app.aclTs) + for i, _ := range aclKeys { + aclEntry, _ := d.GetEntry(app.aclTs, aclKeys[i]) + app.aclTableMap[aclKeys[i].Get(0)] = aclEntry + } + } + + for aclName, _ := range app.aclTableMap { + aclData := app.aclTableMap[aclName] + aclIntfs := aclData.GetList("ports") + aclType := aclData.Get(ACL_TYPE) + var aclOrigName string + var aclOrigType ocbinds.E_OpenconfigAcl_ACL_TYPE + if SONIC_ACL_TYPE_IPV4 == aclType { + aclOrigName = strings.Replace(aclName, "_"+OPENCONFIG_ACL_TYPE_IPV4, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 + } else if SONIC_ACL_TYPE_IPV6 == aclType { + aclOrigName = strings.Replace(aclName, "_"+OPENCONFIG_ACL_TYPE_IPV6, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 + } else if SONIC_ACL_TYPE_L2 == aclType { + aclOrigName = strings.Replace(aclName, "_"+OPENCONFIG_ACL_TYPE_L2, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 + } + + if contains(aclIntfs, intfId) && direction == aclData.Get("stage") { + if direction == "INGRESS" { + if intfData.IngressAclSets != nil { + aclSetKey := ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_Key{SetName: aclOrigName, Type: aclOrigType} + ingressAclSet, ok := intfData.IngressAclSets.IngressAclSet[aclSetKey] + if !ok { + ingressAclSet, _ = intfData.IngressAclSets.NewIngressAclSet(aclOrigName, aclOrigType) + ygot.BuildEmptyTree(ingressAclSet) + ingressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_Config{SetName: &aclOrigName, Type: aclOrigType} + ingressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_State{SetName: &aclOrigName, Type: aclOrigType} + } + err = app.convertInternalToOCAclBinding(d, aclName, intfId, direction, ingressAclSet) + if err != nil { + return err + } + } + } else if direction == "EGRESS" { + if intfData.EgressAclSets != nil { + aclSetKey := ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_Key{SetName: aclOrigName, Type: aclOrigType} + egressAclSet, ok := intfData.EgressAclSets.EgressAclSet[aclSetKey] + if !ok { + egressAclSet, _ = intfData.EgressAclSets.NewEgressAclSet(aclOrigName, aclOrigType) + ygot.BuildEmptyTree(egressAclSet) + egressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_Config{SetName: &aclOrigName, Type: aclOrigType} + egressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_State{SetName: &aclOrigName, Type: aclOrigType} + } + err = app.convertInternalToOCAclBinding(d, aclName, intfId, direction, egressAclSet) + if err != nil { + return err + } + } + } + } + } + return err +} + +/*func (app *AclApp) isInterfaceBindWithACL(d *db.DB, intfId string) bool { + var isFound bool = false + + if len(app.aclTableMap) == 0 { + aclKeys, _ := d.GetKeys(app.aclTs) + for i, _ := range aclKeys { + aclEntry, _ := d.GetEntry(app.aclTs, aclKeys[i]) + app.aclTableMap[(aclKeys[i]).Get(0)] = aclEntry + } + } + + var interfaces []string + for aclName := range app.aclTableMap { + aclData := app.aclTableMap[aclName] + if len(aclData.Get("ports@")) > 0 { + aclIntfs := aclData.GetList("ports") + for i, _ := range aclIntfs { + if !contains(interfaces, aclIntfs[i]) && aclIntfs[i] != "" { + interfaces = append(interfaces, aclIntfs[i]) + } + } + } + } + + isFound = contains(interfaces, intfId) + return isFound +}*/ + +func (app *AclApp) handleBindingsDeletion(d *db.DB) error { + var err error + + acl := app.getAppRootObject() + aclKeys, _ := d.GetKeys(app.aclTs) + for i, _ := range aclKeys { + aclEntry, _ := d.GetEntry(app.aclTs, aclKeys[i]) + var isRequestedAclFound = false + if len(aclEntry.GetList("ports")) > 0 { + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}") { + direction := aclEntry.Get("stage") + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}/ingress-acl-sets") && direction != "INGRESS" { + return tlerr.InvalidArgs("Acl %s is not Ingress", aclKeys[i].Get(0)) + } + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}/egress-acl-sets") && direction != "EGRESS" { + return tlerr.InvalidArgs("Acl %s is not Egress", aclKeys[i].Get(0)) + } + for intfId := range acl.Interfaces.Interface { + aclname, acltype := getAclKeysFromStrKey(aclKeys[i].Get(0), aclEntry.Get("type")) + intfData := acl.Interfaces.Interface[intfId] + if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}/ingress-acl-sets/ingress-acl-set{}{}") { + for k := range intfData.IngressAclSets.IngressAclSet { + if aclname == k.SetName { + if acltype == k.Type { + isRequestedAclFound = true + } else { + return tlerr.InvalidArgs("Acl Type is not matching") + } + } else { + goto SkipDBProcessing + } + } + } else if isSubtreeRequest(app.pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}/egress-acl-sets/egress-acl-set{}{}") { + for k := range intfData.EgressAclSets.EgressAclSet { + if aclname == k.SetName { + if acltype == k.Type { + isRequestedAclFound = true + } else { + return tlerr.InvalidArgs("Acl Type is not matching") + } + } else { + goto SkipDBProcessing + } + } + } + intfs := aclEntry.GetList("ports") + intfs = removeElement(intfs, intfId) + aclEntry.SetList("ports", intfs) + err = d.SetEntry(app.aclTs, aclKeys[i], aclEntry) + if err != nil { + return err + } + // If last interface removed, then remove stage field also + if len(intfs) == 0 { + aclEntry.Remove("stage") + } + } + SkipDBProcessing: + } else { + aclEntry.Remove("stage") + aclEntry.SetList("ports", []string{}) + err = d.SetEntry(app.aclTs, aclKeys[i], aclEntry) + if err != nil { + return err + } + } + } + if isRequestedAclFound { + break + } + } + + return err +} + +/******************** CREATE related *******************************/ +func (app *AclApp) convertOCAclsToInternal() { + acl := app.getAppRootObject() + if acl != nil { + app.aclTableMap = make(map[string]db.Value) + if acl.AclSets != nil && len(acl.AclSets.AclSet) > 0 { + for aclSetKey, _ := range acl.AclSets.AclSet { + aclSet := acl.AclSets.AclSet[aclSetKey] + aclKey := getAclKeyStrFromOCKey(aclSetKey.Name, aclSetKey.Type) + app.aclTableMap[aclKey] = db.Value{Field: map[string]string{}} + + if aclSet.Config != nil { + if aclSet.Config.Type == ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 { + app.aclTableMap[aclKey].Field[ACL_TYPE] = SONIC_ACL_TYPE_IPV4 + } else if aclSet.Config.Type == ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 { + app.aclTableMap[aclKey].Field[ACL_TYPE] = SONIC_ACL_TYPE_IPV6 + } else if aclSet.Config.Type == ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 { + app.aclTableMap[aclKey].Field[ACL_TYPE] = SONIC_ACL_TYPE_L2 + } + + if aclSet.Config.Description != nil && len(*aclSet.Config.Description) > 0 { + app.aclTableMap[aclKey].Field[ACL_DESCRIPTION] = *aclSet.Config.Description + } + } + } + } + } +} + +func (app *AclApp) convertOCAclRulesToInternal(d *db.DB) { + acl := app.getAppRootObject() + if acl != nil { + app.ruleTableMap = make(map[string]map[string]db.Value) + if acl.AclSets != nil && len(acl.AclSets.AclSet) > 0 { + for aclSetKey, _ := range acl.AclSets.AclSet { + aclSet := acl.AclSets.AclSet[aclSetKey] + aclKey := getAclKeyStrFromOCKey(aclSetKey.Name, aclSetKey.Type) + app.ruleTableMap[aclKey] = make(map[string]db.Value) + + if aclSet.AclEntries != nil { + for seqId, _ := range aclSet.AclEntries.AclEntry { + entrySet := aclSet.AclEntries.AclEntry[seqId] + ruleName := "RULE_" + strconv.Itoa(int(seqId)) + app.ruleTableMap[aclKey][ruleName] = db.Value{Field: map[string]string{}} + convertOCAclRuleToInternalAclRule(app.ruleTableMap[aclKey][ruleName], seqId, aclKey, aclSet.Type, entrySet) + } + } + + yangPathStr, _ := getYangPathFromUri(app.pathInfo.Path) + if yangPathStr != "/openconfig-acl:acl/acl-sets/acl-set/acl-entries" && yangPathStr != "/openconfig-acl:acl/acl-sets/acl-set/acl-entries/acl-entry" { + app.createDefaultDenyAclRule(d, aclKey, app.ruleTableMap[aclKey]) + } + } + } + } +} + +func (app *AclApp) convertOCAclBindingsToInternal() { + aclObj := app.getAppRootObject() + + if aclObj.Interfaces != nil && len(aclObj.Interfaces.Interface) > 0 { + aclInterfacesMap := make(map[string][]string) + // Below code assumes that an ACL can be either INGRESS or EGRESS but not both. + for intfId, _ := range aclObj.Interfaces.Interface { + intf := aclObj.Interfaces.Interface[intfId] + if intf != nil { + if intf.IngressAclSets != nil && len(intf.IngressAclSets.IngressAclSet) > 0 { + for inAclKey, _ := range intf.IngressAclSets.IngressAclSet { + aclName := getAclKeyStrFromOCKey(inAclKey.SetName, inAclKey.Type) + // TODO: Need to handle Subinterface also + if intf.InterfaceRef != nil && intf.InterfaceRef.Config.Interface != nil { + aclInterfacesMap[aclName] = append(aclInterfacesMap[aclName], *intf.InterfaceRef.Config.Interface) + } else { + aclInterfacesMap[aclName] = append(aclInterfacesMap[aclName], *intf.Id) + } + if len(app.aclTableMap) == 0 { + app.aclTableMap[aclName] = db.Value{Field: map[string]string{}} + } + app.aclTableMap[aclName].Field["stage"] = "INGRESS" + } + } + + if intf.EgressAclSets != nil && len(intf.EgressAclSets.EgressAclSet) > 0 { + for outAclKey, _ := range intf.EgressAclSets.EgressAclSet { + aclName := getAclKeyStrFromOCKey(outAclKey.SetName, outAclKey.Type) + if intf.InterfaceRef != nil && intf.InterfaceRef.Config.Interface != nil { + aclInterfacesMap[aclName] = append(aclInterfacesMap[aclName], *intf.InterfaceRef.Config.Interface) + } else { + aclInterfacesMap[aclName] = append(aclInterfacesMap[aclName], *intf.Id) + } + if len(app.aclTableMap) == 0 { + app.aclTableMap[aclName] = db.Value{Field: map[string]string{}} + } + app.aclTableMap[aclName].Field["stage"] = "EGRESS" + } + } + } + } + for k, _ := range aclInterfacesMap { + val := app.aclTableMap[k] + (&val).SetList("ports", aclInterfacesMap[k]) + } + } +} + +func (app *AclApp) createDefaultDenyAclRule(d *db.DB, aclName string, rulesInfo map[string]db.Value) { + existingRuleEntry, err := d.GetEntry(app.ruleTs, db.Key{Comp: []string{aclName, "DEFAULT_RULE"}}) + // If Default Rule already exists, Do not add new Default Rule + if existingRuleEntry.IsPopulated() && err == nil { + return + } + m := make(map[string]string) + rulesInfo["DEFAULT_RULE"] = db.Value{Field: m} + rulesInfo["DEFAULT_RULE"].Field["PRIORITY"] = strconv.FormatInt(int64(MIN_PRIORITY), 10) + rulesInfo["DEFAULT_RULE"].Field["PACKET_ACTION"] = "DROP" + rulesInfo["DEFAULT_RULE"].Field["IP_TYPE"] = "ANY" +} + +func convertOCAclRuleToInternalAclRule(ruleData db.Value, seqId uint32, aclName string, aclType ocbinds.E_OpenconfigAcl_ACL_TYPE, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + ruleIndex := seqId + ruleData.Field["PRIORITY"] = strconv.FormatInt(int64(MAX_PRIORITY-ruleIndex), 10) + // Rule Description is not supported in Sonic. So commenting this out. + /* + if rule.Config != nil && rule.Config.Description != nil { + ruleData.Field["RULE_DESCRIPTION"] = *rule.Config.Description + } + */ + + if ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 == aclType { + convertOCToInternalIPv4(ruleData, aclName, ruleIndex, rule) + } else if ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 == aclType { + convertOCToInternalIPv6(ruleData, aclName, ruleIndex, rule) + } else if ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 == aclType { + convertOCToInternalL2(ruleData, aclName, ruleIndex, rule) + } /*else if ocbinds.OpenconfigAcl_ACL_TYPE_ACL_MIXED == aclType { + } */ + + convertOCToInternalTransport(ruleData, aclName, ruleIndex, rule) + convertOCToInternalInputInterface(ruleData, aclName, ruleIndex, rule) + convertOCToInternalInputAction(ruleData, aclName, ruleIndex, rule) +} + +func convertOCToInternalL2(ruleData db.Value, aclName string, ruleIndex uint32, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if rule.L2 == nil { + return + } + if rule.L2.Config.Ethertype != nil && util.IsTypeStructPtr(reflect.TypeOf(rule.L2.Config.Ethertype)) { + ethertypeType := reflect.TypeOf(rule.L2.Config.Ethertype).Elem() + var b bytes.Buffer + switch ethertypeType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_E_OpenconfigPacketMatchTypes_ETHERTYPE{}): + v := (rule.L2.Config.Ethertype).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_E_OpenconfigPacketMatchTypes_ETHERTYPE) + //ruleData["ETHER_TYPE"] = v.E_OpenconfigPacketMatchTypes_ETHERTYPE.ΛMap()["E_OpenconfigPacketMatchTypes_ETHERTYPE"][int64(v.E_OpenconfigPacketMatchTypes_ETHERTYPE)].Name + fmt.Fprintf(&b, "0x%0.4x", ETHERTYPE_MAP[v.E_OpenconfigPacketMatchTypes_ETHERTYPE]) + ruleData.Field["ETHER_TYPE"] = b.String() + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_Uint16{}): + v := (rule.L2.Config.Ethertype).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_Uint16) + fmt.Fprintf(&b, "0x%0.4x", v.Uint16) + ruleData.Field["ETHER_TYPE"] = b.String() + } + } +} + +func convertOCToInternalIPv4(ruleData db.Value, aclName string, ruleIndex uint32, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if rule.Ipv4.Config.Protocol != nil && util.IsTypeStructPtr(reflect.TypeOf(rule.Ipv4.Config.Protocol)) { + protocolType := reflect.TypeOf(rule.Ipv4.Config.Protocol).Elem() + switch protocolType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_E_OpenconfigPacketMatchTypes_IP_PROTOCOL{}): + v := (rule.Ipv4.Config.Protocol).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_E_OpenconfigPacketMatchTypes_IP_PROTOCOL) + //ruleData["IP_PROTOCOL"] = v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL.ΛMap()["E_OpenconfigPacketMatchTypes_IP_PROTOCOL"][int64(v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL)].Name + ruleData.Field["IP_PROTOCOL"] = strconv.FormatInt(int64(IP_PROTOCOL_MAP[v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL]), 10) + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_Uint8{}): + v := (rule.Ipv4.Config.Protocol).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_Uint8) + ruleData.Field["IP_PROTOCOL"] = strconv.FormatInt(int64(v.Uint8), 10) + } + } + + if rule.Ipv4.Config.Dscp != nil { + ruleData.Field["DSCP"] = strconv.FormatInt(int64(*rule.Ipv4.Config.Dscp), 10) + } + if rule.Ipv4.Config.SourceAddress != nil { + ruleData.Field["SRC_IP"] = *rule.Ipv4.Config.SourceAddress + } + if rule.Ipv4.Config.DestinationAddress != nil { + ruleData.Field["DST_IP"] = *rule.Ipv4.Config.DestinationAddress + } +} + +func convertOCToInternalIPv6(ruleData db.Value, aclName string, ruleIndex uint32, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if rule.Ipv6.Config.Protocol != nil && util.IsTypeStructPtr(reflect.TypeOf(rule.Ipv6.Config.Protocol)) { + protocolType := reflect.TypeOf(rule.Ipv6.Config.Protocol).Elem() + switch protocolType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_Config_Protocol_Union_E_OpenconfigPacketMatchTypes_IP_PROTOCOL{}): + v := (rule.Ipv6.Config.Protocol).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_Config_Protocol_Union_E_OpenconfigPacketMatchTypes_IP_PROTOCOL) + //ruleData["IP_PROTOCOL"] = v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL.ΛMap()["E_OpenconfigPacketMatchTypes_IP_PROTOCOL"][int64(v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL)].Name + ruleData.Field["IP_PROTOCOL"] = strconv.FormatInt(int64(IP_PROTOCOL_MAP[v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL]), 10) + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_Config_Protocol_Union_Uint8{}): + v := (rule.Ipv6.Config.Protocol).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_Config_Protocol_Union_Uint8) + ruleData.Field["IP_PROTOCOL"] = strconv.FormatInt(int64(v.Uint8), 10) + } + } + + if rule.Ipv6.Config.Dscp != nil { + ruleData.Field["DSCP"] = strconv.FormatInt(int64(*rule.Ipv6.Config.Dscp), 10) + } + if rule.Ipv6.Config.SourceAddress != nil { + ruleData.Field["SRC_IPV6"] = *rule.Ipv6.Config.SourceAddress + } + if rule.Ipv6.Config.DestinationAddress != nil { + ruleData.Field["DST_IPV6"] = *rule.Ipv6.Config.DestinationAddress + } + if rule.Ipv6.Config.SourceFlowLabel != nil { + ruleData.Field["SRC_FLOWLABEL"] = strconv.FormatInt(int64(*rule.Ipv6.Config.SourceFlowLabel), 10) + } + if rule.Ipv6.Config.DestinationFlowLabel != nil { + ruleData.Field["DST_FLOWLABEL"] = strconv.FormatInt(int64(*rule.Ipv6.Config.DestinationFlowLabel), 10) + } +} + +func convertOCToInternalTransport(ruleData db.Value, aclName string, ruleIndex uint32, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if rule.Transport == nil { + return + } + if rule.Transport.Config.SourcePort != nil && util.IsTypeStructPtr(reflect.TypeOf(rule.Transport.Config.SourcePort)) { + sourceportType := reflect.TypeOf(rule.Transport.Config.SourcePort).Elem() + switch sourceportType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort{}): + v := (rule.Transport.Config.SourcePort).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort) + ruleData.Field["L4_SRC_PORT"] = v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort.ΛMap()["E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort"][int64(v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort)].Name + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_String{}): + v := (rule.Transport.Config.SourcePort).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_String) + ruleData.Field["L4_SRC_PORT_RANGE"] = strings.Replace(v.String, "..", "-", 1) + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_Uint16{}): + v := (rule.Transport.Config.SourcePort).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_Uint16) + ruleData.Field["L4_SRC_PORT"] = strconv.FormatInt(int64(v.Uint16), 10) + } + } + + if rule.Transport.Config.DestinationPort != nil && util.IsTypeStructPtr(reflect.TypeOf(rule.Transport.Config.DestinationPort)) { + destportType := reflect.TypeOf(rule.Transport.Config.DestinationPort).Elem() + switch destportType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort{}): + v := (rule.Transport.Config.DestinationPort).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort) + ruleData.Field["L4_DST_PORT"] = v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort.ΛMap()["E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort"][int64(v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort)].Name + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_String{}): + v := (rule.Transport.Config.DestinationPort).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_String) + ruleData.Field["L4_DST_PORT_RANGE"] = strings.Replace(v.String, "..", "-", 1) + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_Uint16{}): + v := (rule.Transport.Config.DestinationPort).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_Uint16) + ruleData.Field["L4_DST_PORT"] = strconv.FormatInt(int64(v.Uint16), 10) + } + } + + var tcpFlags uint32 = 0x00 + if len(rule.Transport.Config.TcpFlags) > 0 { + for _, flag := range rule.Transport.Config.TcpFlags { + switch flag { + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_FIN: + tcpFlags |= 0x01 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_SYN: + tcpFlags |= 0x02 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_RST: + tcpFlags |= 0x04 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_PSH: + tcpFlags |= 0x08 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ACK: + tcpFlags |= 0x10 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_URG: + tcpFlags |= 0x20 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ECE: + tcpFlags |= 0x40 + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_CWR: + tcpFlags |= 0x80 + } + } + var b bytes.Buffer + fmt.Fprintf(&b, "0x%0.2x/0x%0.2x", tcpFlags, tcpFlags) + ruleData.Field["TCP_FLAGS"] = b.String() + } +} + +func convertOCToInternalInputInterface(ruleData db.Value, aclName string, ruleIndex uint32, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if rule.InputInterface != nil && rule.InputInterface.InterfaceRef != nil { + ruleData.Field["IN_PORTS"] = *rule.InputInterface.InterfaceRef.Config.Interface + } +} + +func convertOCToInternalInputAction(ruleData db.Value, aclName string, ruleIndex uint32, rule *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry) { + if rule.Actions != nil && rule.Actions.Config != nil { + switch rule.Actions.Config.ForwardingAction { + case ocbinds.OpenconfigAcl_FORWARDING_ACTION_ACCEPT: + ruleData.Field["PACKET_ACTION"] = "FORWARD" + case ocbinds.OpenconfigAcl_FORWARDING_ACTION_DROP, ocbinds.OpenconfigAcl_FORWARDING_ACTION_REJECT: + ruleData.Field["PACKET_ACTION"] = "DROP" + default: + } + } +} + +func (app *AclApp) handleRuleFieldsDeletion(d *db.DB, aclKey string, ruleKey string) error { + var err error + + ruleEntry, err := d.GetEntry(app.ruleTs, asKey(aclKey, ruleKey)) + if err != nil { + return err + } + nodeInfo, err := getTargetNodeYangSchema(app.pathInfo.Path, (*app.ygotRoot).(*ocbinds.Device)) + if err != nil { + return err + } + if nodeInfo.IsLeaf() { + switch nodeInfo.Name { + case "description": + (&ruleEntry).Remove("RULE_DESCRIPTION") + // L2 + case "ethertype": + (&ruleEntry).Remove("ETHER_TYPE") + // IPv4/IPv6 + case "source-address": + if strings.Contains(app.pathInfo.Path, "ipv4/config") { + (&ruleEntry).Remove("SRC_IP") + } else if strings.Contains(app.pathInfo.Path, "ipv6/config") { + (&ruleEntry).Remove("SRC_IPV6") + } + case "destination-address": + if strings.Contains(app.pathInfo.Path, "ipv4/config") { + (&ruleEntry).Remove("DST_IP") + } else if strings.Contains(app.pathInfo.Path, "ipv6/config") { + (&ruleEntry).Remove("DST_IPV6") + } + case "dscp": + (&ruleEntry).Remove("DSCP") + case "protocol": + (&ruleEntry).Remove("IP_PROTOCOL") + // transport + case "source-port": + (&ruleEntry).Remove("L4_SRC_PORT") + (&ruleEntry).Remove("L4_SRC_PORT_RANGE") + case "destination-port": + (&ruleEntry).Remove("L4_DST_PORT") + (&ruleEntry).Remove("L4_DST_PORT_RANGE") + // actions + case "forwarding-action": + (&ruleEntry).Remove("PACKET_ACTION") + //input-interface + case "interface": + (&ruleEntry).Remove("IN_PORTS") + //case "subinterface": + } + } else if nodeInfo.IsContainer() { + targetType := reflect.TypeOf(*app.ygotTarget) + switch targetType.Elem().Name() { + case "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config": + (&ruleEntry).Remove("ETHER_TYPE") + case "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config": + (&ruleEntry).Remove("IP_PROTOCOL") + (&ruleEntry).Remove("SRC_IP") + (&ruleEntry).Remove("DST_IP") + (&ruleEntry).Remove("DSCP") + case "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv6_Config": + (&ruleEntry).Remove("IP_PROTOCOL") + (&ruleEntry).Remove("SRC_IPV6") + (&ruleEntry).Remove("DST_IPV6") + (&ruleEntry).Remove("DSCP") + case "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config": + (&ruleEntry).Remove("L4_SRC_PORT") + (&ruleEntry).Remove("L4_SRC_PORT_RANGE") + (&ruleEntry).Remove("L4_DST_PORT") + (&ruleEntry).Remove("L4_DST_PORT_RANGE") + (&ruleEntry).Remove("TCP_FLAGS") + case "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_InputInterface", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_InputInterface_InterfaceRef", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_InputInterface_InterfaceRef_Config": + (&ruleEntry).Remove("IN_PORTS") + case "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Actions", "OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Actions_Config": + (&ruleEntry).Remove("PACKET_ACTION") + } + } else if nodeInfo.IsLeafList() { + switch nodeInfo.Name { + case "tcp-flags": + (&ruleEntry).Remove("TCP_FLAGS") + } + } else { + log.Error("This yang type is not handled currently") + } + err = d.SetEntry(app.ruleTs, asKey(aclKey, ruleKey), ruleEntry) + + return err +} + +func (app *AclApp) setAclDataInConfigDb(d *db.DB, aclData map[string]db.Value, createFlag bool) error { + var err error + for key := range aclData { + existingEntry, err := d.GetEntry(app.aclTs, db.Key{Comp: []string{key}}) + // If Create ACL request comes and ACL already exists, throw error + if createFlag && existingEntry.IsPopulated() { + return tlerr.AlreadyExists("Acl %s already exists", key) + } + if createFlag || (!createFlag && err != nil && !existingEntry.IsPopulated()) { + err := d.CreateEntry(app.aclTs, db.Key{Comp: []string{key}}, aclData[key]) + if err != nil { + return err + } + } else { + if existingEntry.IsPopulated() { + if existingEntry.Get(ACL_DESCRIPTION) != aclData[key].Field[ACL_DESCRIPTION] { + err := d.ModEntry(app.aclTs, db.Key{Comp: []string{key}}, aclData[key]) + if err != nil { + return err + } + } + } + } + } + return err +} + +func (app *AclApp) setAclRuleDataInConfigDb(d *db.DB, ruleData map[string]map[string]db.Value, createFlag bool) error { + var err error + for aclName := range ruleData { + for ruleName := range ruleData[aclName] { + existingRuleEntry, err := d.GetEntry(app.ruleTs, db.Key{Comp: []string{aclName, ruleName}}) + // If Create Rule request comes and Rule already exists, throw error + if createFlag && existingRuleEntry.IsPopulated() { + return tlerr.AlreadyExists("Rule %s already exists", ruleName) + } + if createFlag || (!createFlag && err != nil && !existingRuleEntry.IsPopulated()) { + err := d.CreateEntry(app.ruleTs, db.Key{Comp: []string{aclName, ruleName}}, ruleData[aclName][ruleName]) + if err != nil { + return err + } + } else { + if existingRuleEntry.IsPopulated() && ruleName != "DEFAULT_RULE" { + err := d.ModEntry(app.ruleTs, db.Key{Comp: []string{aclName, ruleName}}, ruleData[aclName][ruleName]) + if err != nil { + return err + } + } + } + } + } + return err +} + +func (app *AclApp) setAclBindDataInConfigDb(d *db.DB, aclData map[string]db.Value, opcode int) error { + var err error + for aclKey, aclInfo := range aclData { + // Get ACL info from DB + dbAcl, err := d.GetEntry(app.aclTs, db.Key{Comp: []string{aclKey}}) + if err != nil { + return err + } + if REPLACE == opcode { + dbAcl.SetList("ports", aclInfo.GetList("ports")) + dbAcl.Set("stage", aclInfo.Get("stage")) + } else { + dbAclIntfs := dbAcl.GetList("ports") + if len(dbAclIntfs) > 0 { + dbAclDirec := dbAcl.Get("stage") + newDirec := aclInfo.Get("stage") + if (UPDATE == opcode) && (len(dbAclDirec) > 0) && (len(newDirec) > 0) && (dbAclDirec != newDirec) { + return tlerr.InvalidArgs("Acl direction of %s not allowed when it is already configured as %s", newDirec, dbAclDirec) + } + // Merge interfaces from DB to list in aclInfo and set back in DB + intfs := aclInfo.GetList("ports") + for _, ifId := range dbAclIntfs { + if !contains(intfs, ifId) { + intfs = append(intfs, ifId) + } + } + dbAcl.SetList("ports", intfs) + } else { + dbAcl.SetList("ports", aclInfo.GetList("ports")) + } + + if len(dbAcl.Get("stage")) == 0 { + dbAcl.Set("stage", aclInfo.Get("stage")) + } + } + err = d.SetEntry(app.aclTs, db.Key{Comp: []string{aclKey}}, dbAcl) + //err = d.ModEntry(app.aclTs, db.Key{Comp: []string{aclKey}}, dbAcl) + if err != nil { + return err + } + } + return err +} + +func getIpProtocol(proto int64) interface{} { + for k, v := range IP_PROTOCOL_MAP { + if uint8(proto) == v { + return k + } + } + return uint8(proto) +} + +func getTransportSrcDestPorts(portVal string, portType string) interface{} { + var portRange string = "" + + portNum, err := strconv.Atoi(portVal) + if err != nil && strings.Contains(portVal, "-") { + portRange = portVal + } + + if len(portRange) > 0 { + return portRange + } else if portNum > 0 { + return uint16(portNum) + } else { + if "src" == portType { + return ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_ANY + } else if "dest" == portType { + return ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_ANY + } + } + return nil +} + +func getTransportConfigTcpFlags(tcpFlags string) []ocbinds.E_OpenconfigPacketMatchTypes_TCP_FLAGS { + var flags []ocbinds.E_OpenconfigPacketMatchTypes_TCP_FLAGS + if len(tcpFlags) > 0 { + flagStr := strings.Split(tcpFlags, "/")[0] + flagNumber, _ := strconv.ParseUint(strings.Replace(flagStr, "0x", "", -1), 16, 32) + for i := 0; i < 8; i++ { + mask := 1 << uint(i) + if (int(flagNumber) & mask) > 0 { + switch int(flagNumber) & mask { + case 0x01: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_FIN) + case 0x02: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_SYN) + case 0x04: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_RST) + case 0x08: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_PSH) + case 0x10: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ACK) + case 0x20: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_URG) + case 0x40: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ECE) + case 0x80: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_CWR) + default: + } + } + } + } + return flags +} + +func getL2EtherType(etherType uint64) interface{} { + for k, v := range ETHERTYPE_MAP { + if uint32(etherType) == v { + return k + } + } + return uint16(etherType) +} + +func getAclKeysFromStrKey(aclKey string, aclType string) (string, ocbinds.E_OpenconfigAcl_ACL_TYPE) { + var aclOrigName string + var aclOrigType ocbinds.E_OpenconfigAcl_ACL_TYPE + + if SONIC_ACL_TYPE_IPV4 == aclType { + aclOrigName = strings.Replace(aclKey, "_"+OPENCONFIG_ACL_TYPE_IPV4, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 + } else if SONIC_ACL_TYPE_IPV6 == aclType { + aclOrigName = strings.Replace(aclKey, "_"+OPENCONFIG_ACL_TYPE_IPV6, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 + } else if SONIC_ACL_TYPE_L2 == aclType { + aclOrigName = strings.Replace(aclKey, "_"+OPENCONFIG_ACL_TYPE_L2, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 + } + return aclOrigName, aclOrigType +} + +// getAclTypeOCEnumFromName returns the ACL_TYPE enum from name +func getAclTypeOCEnumFromName(val string) (ocbinds.E_OpenconfigAcl_ACL_TYPE, error) { + switch val { + case "ACL_IPV4", "openconfig-acl:ACL_IPV4": + return ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4, nil + case "ACL_IPV6", "openconfig-acl:ACL_IPV6": + return ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6, nil + case "ACL_L2", "openconfig-acl:ACL_L2": + return ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2, nil + default: + return ocbinds.OpenconfigAcl_ACL_TYPE_UNSET, + tlerr.NotSupported("ACL Type '%s' not supported", val) + } +} + +func getAclKeyStrFromOCKey(aclname string, acltype ocbinds.E_OpenconfigAcl_ACL_TYPE) string { + aclN := strings.Replace(strings.Replace(aclname, " ", "_", -1), "-", "_", -1) + aclT := acltype.ΛMap()["E_OpenconfigAcl_ACL_TYPE"][int64(acltype)].Name + return aclN + "_" + aclT +} + +/* Check if targetUriPath is child (subtree) of nodePath +The return value can be used to decide if subtrees needs +to visited to fill the data or not. +*/ +func isSubtreeRequest(targetUriPath string, nodePath string) bool { + return strings.HasPrefix(targetUriPath, nodePath) +} diff --git a/translib/acl_app_test.go b/translib/acl_app_test.go new file mode 100644 index 000000000..729c288b5 --- /dev/null +++ b/translib/acl_app_test.go @@ -0,0 +1,572 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "errors" + "fmt" + "os" + "reflect" + "strings" + "testing" + db "github.com/Azure/sonic-mgmt-common/translib/db" +) + +func init() { + fmt.Println("+++++ Init acl_app_test +++++") +} + +func TestMain(m *testing.M) { + if err := clearAclDataFromDb(); err != nil { + os.Exit(-1) + } + fmt.Println("+++++ Removed All Acl Data from Db +++++") + + ret := m.Run() + + if err := clearAclDataFromDb(); err != nil { + os.Exit(-1) + } + + os.Exit(ret) +} + +// This will test GET on /openconfig-acl:acl +func Test_AclApp_TopLevelPath(t *testing.T) { + url := "/openconfig-acl:acl" + + t.Run("Empty_Response_Top_Level", processGetRequest(url, emptyJson, false)) + + t.Run("Bulk_Create_Top_Level", processSetRequest(url, bulkAclCreateJsonRequest, "POST", false)) + + t.Run("Get_Full_Acl_Tree_Top_Level", processGetRequest(url, bulkAclCreateJsonResponse, false)) + + // Delete all bindings before deleting at top level + t.Run("Delete_All_Bindings_Top_Level", processDeleteRequest("/openconfig-acl:acl/interfaces")) + t.Run("Delete_Full_ACl_Tree_Top_Level", processDeleteRequest(url)) + + t.Run("Verify_Top_Level_Delete", processGetRequest(url, emptyJson, false)) +} + +func Test_AclApp_SingleAclOperations(t *testing.T) { + url := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL3][type=ACL_IPV4]" + + t.Run("Create_One_Acl_With_Multiple_Rules(PATCH)", processSetRequest(url, oneAclCreateWithRulesJsonRequest, "PATCH", false)) + + t.Run("Verify_Create_One_Acl_With_Multiple_Rules", processGetRequest(url, oneAclCreateWithRulesJsonResponse, false)) + + aclDescrUrl := url + "/config/description" + t.Run("Delete Acl_Description", processDeleteRequest(aclDescrUrl)) + t.Run("Verify_Acl_Description_Deletion", processGetRequest(aclDescrUrl, emptyAclDescriptionJson, false)) + + createAclDescrUrl := url + "/config" + t.Run("Create_new_Acl_Description", processSetRequest(createAclDescrUrl, aclDescrUpdateJson, "POST", false)) + t.Run("Verify_Description_of_Acl", processGetRequest(aclDescrUrl, aclDescrUpdateJson, false)) + + t.Run("Delete_One_Acl_With_All_Its_Rules", processDeleteRequest(url)) + + t.Run("Verify_One_Acl_Delete", processGetRequest(url, "", true)) +} + +func Test_AclApp_SingleRuleOperations(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=8]" + + t.Run("Create_One_Acl_Without_Rule", processSetRequest(aclUrl, oneAclCreateJsonRequest, "POST", false)) + t.Run("Get_One_Acl_Without_Rule", processGetRequest(aclUrl, oneAclCreateJsonResponse, false)) + + t.Run("Create_One_Rule", processSetRequest(ruleUrl, requestOneRulePostJson, "POST", false)) + t.Run("Get_One_Rule", processGetRequest(ruleUrl, responseOneRuleJson, false)) + + // Change Source/Desination address and protocol + t.Run("Update_Existing_Rule", processSetRequest(ruleUrl, requestOneRulePatchJson, "PATCH", false)) + t.Run("Verify_One_Rule_Updation", processGetRequest(ruleUrl, responseOneRulePatchJson, false)) + + tcpFlagsUrl := ruleUrl + "/transport/config/tcp-flags" + t.Run("Delete_Tcp_Flags_Field", processDeleteRequest(tcpFlagsUrl)) + t.Run("Verify_Tcp_Flags_Deletion", processGetRequest(tcpFlagsUrl, emptyJson, false)) + + dscpUrl := ruleUrl + "/ipv4/config/dscp" + t.Run("Delete_IPv4_Dscp_Field", processDeleteRequest(dscpUrl)) + t.Run("Verify_IPv4_Dscp_Deletion", processGetRequest(dscpUrl, emptyRuleDscpJson, false)) + + protocolUrl := ruleUrl + "/ipv4/config/protocol" + t.Run("Delete_IPv4_Protocol_Field", processDeleteRequest(protocolUrl)) + t.Run("Verify_IPv4_Protocol_Deletion", processGetRequest(protocolUrl, emptyJson, false)) + + transportConfigUrl := ruleUrl + "/transport" + t.Run("Delete_Transport_Container", processDeleteRequest(transportConfigUrl)) + t.Run("Verify_Transport_Container_Deletion", processGetRequest(transportConfigUrl, emptyJson, false)) + + ipv4ConfigUrl := ruleUrl + "/ipv4/config" + t.Run("Delete_IPv4_Config_Container", processDeleteRequest(ipv4ConfigUrl)) + t.Run("Verify_IPv4_Config_Container_Deletion", processGetRequest(ipv4ConfigUrl, emptyJson, false)) + + t.Run("Delete_One_Rule", processDeleteRequest(ruleUrl)) + t.Run("Verify_One_Rule_Delete", processGetRequest(ruleUrl, "", true)) + + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +// This will test PUT (Replace) operation by Replacing multiple Rules with one Rule in an Acl +func Test_AclApp_ReplaceMultipleRulesWithOneRule(t *testing.T) { + url := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL3][type=ACL_IPV4]" + + t.Run("Create_One_Acl_With_Multiple_Rules(PATCH)", processSetRequest(url, oneAclCreateWithRulesJsonRequest, "PATCH", false)) + t.Run("Verify_Create_One_Acl_With_Multiple_Rules", processGetRequest(url, oneAclCreateWithRulesJsonResponse, false)) + + t.Run("Replace_All_Rules_With_One_Rule", processSetRequest(url, replaceMultiRulesWithOneRuleJsonRequest, "PUT", false)) + t.Run("Verify_Acl_With_Replaced_Rules", processGetRequest(url, replaceMultiRulesWithOneRuleJsonResponse, false)) + + t.Run("Delete_One_Acl_With_All_Its_Rules", processDeleteRequest(url)) + t.Run("Verify_One_Acl_Delete", processGetRequest(url, "", true)) +} + +// This will test PATCH operation by modifying Description of an Acl +func Test_AclApp_AclDescriptionUpdation(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]" + descrUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/config/description" + + t.Run("Create_One_Acl_Without_Rule", processSetRequest(aclUrl, oneAclCreateJsonRequest, "POST", false)) + + t.Run("Update_Description_of_Acl", processSetRequest(descrUrl, aclDescrUpdateJson, "PATCH", false)) + t.Run("Verify_Description_of_Acl", processGetRequest(descrUrl, aclDescrUpdateJson, false)) + + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +func Test_AclApp_AclIngressBindingOperations(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=8]" + bindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet4]/ingress-acl-sets/ingress-acl-set[set-name=MyACL5][type=ACL_IPV4]" + + t.Run("Create_One_Acl_Without_Rule", processSetRequest(aclUrl, oneAclCreateJsonRequest, "POST", false)) + + t.Run("Create_One_Rule", processSetRequest(ruleUrl, requestOneRulePostJson, "POST", false)) + + t.Run("Create_Ingress_Acl_set", processSetRequest(bindingUrl, ingressAclSetCreateJsonRequest, "POST", false)) + t.Run("Verify_Ingress_Aclset_Creation", processGetRequest(bindingUrl, ingressAclSetCreateJsonResponse, false)) + t.Run("Get_Port_Binding_From_Ingress_AclEntry_Level", processGetRequest(bindingUrl+"/acl-entries/acl-entry[sequence-id=8]", getBindingAclEntryResponse, false)) + + t.Run("Delete_Binding_From_Ingress_Aclset", processDeleteRequest(bindingUrl)) + t.Run("Verify_Binding_From_Ingress_Aclset_Deletion", processGetRequest(bindingUrl, "", true)) + t.Run("Delete_One_Rule", processDeleteRequest(ruleUrl)) + t.Run("Verify_One_Rule_Delete", processGetRequest(ruleUrl, "", true)) + + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +func Test_AclApp_AclEgressBindingOperations(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=8]" + bindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet4]/egress-acl-sets/egress-acl-set[set-name=MyACL5][type=ACL_IPV4]" + + t.Run("Create_One_Acl_Without_Rule", processSetRequest(aclUrl, oneAclCreateJsonRequest, "POST", false)) + + t.Run("Create_One_Rule", processSetRequest(ruleUrl, requestOneRulePostJson, "POST", false)) + + t.Run("Create_Egress_Acl_set", processSetRequest(bindingUrl, ingressAclSetCreateJsonRequest, "POST", false)) + t.Run("Verify_Egress_Aclset_Creation", processGetRequest(bindingUrl, egressAclSetCreateJsonResponse, false)) + t.Run("Get_Port_Binding_From_Egress_AclEntry_Level", processGetRequest(bindingUrl+"/acl-entries/acl-entry[sequence-id=8]", getBindingAclEntryResponse, false)) + + t.Run("Delete_Binding_From_Egress_Aclset", processDeleteRequest(bindingUrl)) + t.Run("Verify_Binding_From_Egress_Aclset_Deletion", processGetRequest(bindingUrl, "", true)) + t.Run("Delete_One_Rule", processDeleteRequest(ruleUrl)) + t.Run("Verify_One_Rule_Delete", processGetRequest(ruleUrl, "", true)) + + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +func Test_AclApp_GetOperationsFromMultipleTreeLevels(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=8]" + bindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet4]/egress-acl-sets/egress-acl-set[set-name=MyACL5][type=ACL_IPV4]" + + t.Run("Create_One_Acl_Without_Rule", processSetRequest(aclUrl, oneAclCreateJsonRequest, "POST", false)) + t.Run("Create_One_Rule", processSetRequest(ruleUrl, requestOneRulePostJson, "POST", false)) + t.Run("Create_Egress_Acl_set_Port_Binding", processSetRequest(bindingUrl, ingressAclSetCreateJsonRequest, "POST", false)) + + t.Run("Get_Acl_Tree_From_AclSets_level", processGetRequest("/openconfig-acl:acl/acl-sets", getFromAclSetsTreeLevelResponse, false)) + + t.Run("Get_All_Ports_Bindings_From_Interfaces_Tree_Level", processGetRequest("/openconfig-acl:acl/interfaces", getAllPortsFromInterfacesTreeLevelResponse, false)) + + t.Run("Get_One_Port_Binding_From_Interface_Tree_Level", processGetRequest("/openconfig-acl:acl/interfaces/interface[id=Ethernet4]", getPortBindingFromInterfaceTreeLevelResponse, false)) + + t.Run("Delete_Binding_From_Egress_Aclset", processDeleteRequest(bindingUrl)) + t.Run("Verify_Binding_From_Egress_Aclset_Deletion", processGetRequest(bindingUrl, "", true)) + + t.Run("Delete_One_Rule", processDeleteRequest(ruleUrl)) + t.Run("Verify_One_Rule_Delete", processGetRequest(ruleUrl, "", true)) + + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +func Test_AclApp_AddNewPortBindingToAlreadyBindedAcl(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=8]" + bindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet4]/egress-acl-sets/egress-acl-set[set-name=MyACL5][type=ACL_IPV4]" + + t.Run("Create_One_Acl_Without_Rule", processSetRequest(aclUrl, oneAclCreateJsonRequest, "POST", false)) + t.Run("Create_One_Rule", processSetRequest(ruleUrl, requestOneRulePostJson, "POST", false)) + t.Run("Create_Egress_Acl_set_Port_Binding", processSetRequest(bindingUrl, ingressAclSetCreateJsonRequest, "POST", false)) + + newBindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet0]/egress-acl-sets/egress-acl-set[set-name=MyACL5][type=ACL_IPV4]" + t.Run("Create_New_Egress_Acl_set_Port_Binding", processSetRequest(newBindingUrl, ingressAclSetCreateJsonRequest, "POST", false)) + + t.Run("Get_All_Ports_Bindings_From_Interfaces_Tree_Level", processGetRequest("/openconfig-acl:acl/interfaces", getMultiportBindingOnSingleAclResponse, false)) + + t.Run("Delete_All_Bindings_Top_Level", processDeleteRequest("/openconfig-acl:acl/interfaces")) + t.Run("Delete_All_Rules_Not_Acl", processDeleteRequest("/openconfig-acl:acl/acl-sets/acl-set[name=MyACL5][type=ACL_IPV4]/acl-entries")) + + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) + + t.Run("Verify_Top_Level_Delete", processGetRequest("/openconfig-acl:acl", emptyJson, false)) +} + +func Test_AclApp_IPv6AclAndRule(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL6][type=ACL_IPV6]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL6][type=ACL_IPV6]/acl-entries/acl-entry[sequence-id=6]" + bindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet4]/ingress-acl-sets/ingress-acl-set[set-name=MyACL6][type=ACL_IPV6]" + + t.Run("Create_One_IPv6_Acl_Without_Rule", processSetRequest(aclUrl, oneIPv6AclCreateJsonRequest, "POST", false)) + t.Run("Verify_One_IPv6_Acl_Without_Rule_Creation", processGetRequest(aclUrl, oneIPv6AclCreateJsonResponse, false)) + + t.Run("Create_One_IPv6_Rule", processSetRequest(ruleUrl, oneIPv6RuleCreateJsonRequest, "POST", false)) + t.Run("Verify_One_IPv6_Rule_Creation", processGetRequest(ruleUrl, oneIPv6RuleCreateJsonResponse, false)) + + t.Run("Create_Ingress_Acl_set", processSetRequest(bindingUrl, ingressIPv6AclSetCreateJsonRequest, "POST", false)) + t.Run("Verify_Ingress_Aclset_Creation", processGetRequest(bindingUrl, ingressIPv6AclSetCreateJsonResponse, false)) + + t.Run("Get_Acl_Tree_From_AclSet_level", processGetRequest("/openconfig-acl:acl/acl-sets/acl-set", getIPv6AclsFromAclSetListLevelResponse, false)) + t.Run("Get_All_Ports_Bindings_From_Interfaces_Tree_Level", processGetRequest("/openconfig-acl:acl/interfaces", getIPv6AllPortsBindingsResponse, false)) + + t.Run("Delete_Binding_From_Ingress_Aclset", processDeleteRequest(bindingUrl)) + t.Run("Verify_Binding_From_Ingress_Aclset_Deletion", processGetRequest(bindingUrl, "", true)) + + pktActionUrl := ruleUrl + "/actions/config/forwarding-action" + t.Run("Delete_Packet_Action_Field", processDeleteRequest(pktActionUrl)) + t.Run("Verify_Packet_Action_Field_Deletion", processGetRequest(pktActionUrl, emptyJson, false)) + + ipv6ConfigUrl := ruleUrl + "/ipv6/config" + t.Run("Delete_IPv6_Config", processDeleteRequest(ipv6ConfigUrl)) + t.Run("Verify_IPv6_Config_Deletion", processGetRequest(ipv6ConfigUrl, emptyJson, false)) + + t.Run("Delete_One_Rule", processDeleteRequest(ruleUrl)) + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +func Test_AclApp_L2AclAndRule(t *testing.T) { + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL2][type=ACL_L2]" + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL2][type=ACL_L2]/acl-entries/acl-entry[sequence-id=2]" + bindingUrl := "/openconfig-acl:acl/interfaces/interface[id=Ethernet0]/ingress-acl-sets/ingress-acl-set[set-name=MyACL2][type=ACL_L2]" + + t.Run("Create_One_L2_Acl_Without_Rule", processSetRequest(aclUrl, oneL2AclCreateJsonRequest, "POST", false)) + t.Run("Verify_One_L2_Acl_Without_Rule_Creation", processGetRequest(aclUrl, oneL2AclCreateJsonResponse, false)) + + t.Run("Create_One_L2_Rule", processSetRequest(ruleUrl, oneL2RuleCreateJsonRequest, "POST", false)) + t.Run("Verify_One_L2_Rule_Creation", processGetRequest(ruleUrl, oneL2RuleCreateJsonResponse, false)) + + t.Run("Create_Ingress_L2_Acl_set", processSetRequest(bindingUrl, ingressL2AclSetCreateJsonRequest, "POST", false)) + t.Run("Verify_Ingress_L2_Aclset_Creation", processGetRequest(bindingUrl, ingressL2AclSetCreateJsonResponse, false)) + + t.Run("Get_Acl_Tree_From_AclSet_level", processGetRequest("/openconfig-acl:acl/acl-sets/acl-set", getL2AclsFromAclSetListLevelResponse, false)) + t.Run("Get_All_Ports_Bindings_From_Interfaces_Tree_Level", processGetRequest("/openconfig-acl:acl/interfaces", getL2AllPortsBindingsResponse, false)) + + t.Run("Delete_Binding_From_Ingress_Aclset", processDeleteRequest(bindingUrl)) + t.Run("Verify_Binding_From_Ingress_Aclset_Deletion", processGetRequest(bindingUrl, "", true)) + + etherTypeUrl := ruleUrl + "/l2/config/ethertype" + t.Run("Delete_Ethertype_Field", processDeleteRequest(etherTypeUrl)) + t.Run("Verify_L2_Ethertype_Field_Deletion", processGetRequest(ruleUrl+"/l2/config", emptyJson, false)) + + t.Run("Delete_Transport_Src_Port_Field", processDeleteRequest(ruleUrl+"/transport/config/source-port")) + t.Run("Delete_Transport_Dst_Port_Field", processDeleteRequest(ruleUrl+"/transport/config/destination-port")) + t.Run("Delete_Transport_Tcp_Flags_Field", processDeleteRequest(ruleUrl+"/transport/config/tcp-flags")) + t.Run("Verify_Transport_Src_Dst_Fields_Deletion", processGetRequest(ruleUrl+"/transport/config", emptyJson, false)) + + t.Run("Delete_One_Rule", processDeleteRequest(ruleUrl)) + t.Run("Delete_One_Acl", processDeleteRequest(aclUrl)) + t.Run("Verify_One_Acl_Delete", processGetRequest(aclUrl, "", true)) +} + +func Test_AclApp_NegativeTests(t *testing.T) { + // Verify GET returns errors for non-existing ACL + aclUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL2][type=ACL_L2]" + t.Run("Verify_Non_Existing_Acl_GET_Error", processGetRequest(aclUrl, "", true)) + + // Verify GET returns errors for non-existing Rule + ruleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL2][type=ACL_L2]/acl-entries/acl-entry[sequence-id=2]" + t.Run("Verify_Non_Existing_Rule_GET_Error", processGetRequest(ruleUrl, "", true)) + + // Verify Error on giving Invalid Interface in payload during binding creation + url := "/openconfig-acl:acl" + t.Run("Create_Acl_With_Invalid_Interface_Binding", processSetRequest(url, aclCreateWithInvalidInterfaceBinding, "POST", true)) + + // Verify error if duplicate Acl is created using POST + t.Run("Create_One_L2_Acl_Without_Rule", processSetRequest(aclUrl, oneL2AclCreateJsonRequest, "POST", false)) + t.Run("Verify_One_L2_Acl_Without_Rule_Creation", processGetRequest(aclUrl, oneL2AclCreateJsonResponse, false)) + t.Run("Verify_Error_On_Create_Duplicate_L2_Acl", processSetRequest(aclUrl, oneL2AclCreateJsonRequest, "POST", true)) + + // Verify error if duplicate Rule is created using POST + multiRuleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL3][type=ACL_IPV4]" + t.Run("Create_One_Acl_With_Multiple_Rules(PATCH)", processSetRequest(multiRuleUrl, oneAclCreateWithRulesJsonRequest, "PATCH", false)) + t.Run("Verify_Create_One_Acl_With_Multiple_Rules", processGetRequest(multiRuleUrl, oneAclCreateWithRulesJsonResponse, true)) + + duplicateRuleUrl := "/openconfig-acl:acl/acl-sets/acl-set[name=MyACL3][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=1]" + t.Run("Create_One_Duplicate_Rule", processSetRequest(duplicateRuleUrl, requestOneDuplicateRulePostJson, "POST", true)) + + topLevelUrl := "/openconfig-acl:acl" + t.Run("Delete_Full_ACl_Tree_Top_Level", processDeleteRequest(topLevelUrl)) + t.Run("Verify_Top_Level_Delete", processGetRequest(topLevelUrl, emptyJson, false)) +} + +func processGetRequest(url string, expectedRespJson string, errorCase bool) func(*testing.T) { + return func(t *testing.T) { + response, err := Get(GetRequest{url}) + if err != nil && !errorCase { + t.Errorf("Error %v received for Url: %s", err, url) + } + + respJson := response.Payload + if string(respJson) != expectedRespJson { + t.Errorf("Response for Url: %s received is not expected:\n%s", url, string(respJson)) + } + } +} + +func processSetRequest(url string, jsonPayload string, oper string, errorCase bool) func(*testing.T) { + return func(t *testing.T) { + var err error + switch oper { + case "POST": + _, err = Create(SetRequest{Path: url, Payload: []byte(jsonPayload)}) + case "PATCH": + _, err = Update(SetRequest{Path: url, Payload: []byte(jsonPayload)}) + case "PUT": + _, err = Replace(SetRequest{Path: url, Payload: []byte(jsonPayload)}) + default: + t.Errorf("Operation not supported") + } + if err != nil && !errorCase { + t.Errorf("Error %v received for Url: %s", err, url) + } + } +} + +func processDeleteRequest(url string) func(*testing.T) { + return func(t *testing.T) { + _, err := Delete(SetRequest{Path: url}) + if err != nil { + t.Errorf("Error %v received for Url: %s", err, url) + } + } +} + +// THis will delete ACL table and Rules Table from DB +func clearAclDataFromDb() error { + var err error + ruleTable := db.TableSpec{Name: "ACL_RULE"} + aclTable := db.TableSpec{Name: "ACL_TABLE"} + + d := getConfigDb() + if d == nil { + err = errors.New("Failed to connect to config Db") + return err + } + if err = d.DeleteTable(&ruleTable); err != nil { + err = errors.New("Failed to delete Rules Table") + return err + } + if err = d.DeleteTable(&aclTable); err != nil { + err = errors.New("Failed to delete Acl Table") + return err + } + return err +} + +func getConfigDb() *db.DB { + configDb, _ := db.NewDB(db.Options{ + DBNo: db.ConfigDB, + InitIndicator: "CONFIG_DB_INITIALIZED", + TableNameSeparator: "|", + KeySeparator: "|", + }) + + return configDb +} + +func Test_AclApp_Subscribe(t *testing.T) { + app := new(AclApp) + + t.Run("top", testSubsError(app, "/")) + t.Run("unknown", testSubsError(app, "/some/unknown/path")) + t.Run("topacl", testSubsError(app, "/openconfig-acl:acl")) + t.Run("aclsets", testSubsError(app, "/openconfig-acl:acl/acl-sets")) + t.Run("aclset*", testSubsError(app, "/openconfig-acl:acl/acl-sets/acl-set")) + t.Run("aclset", testSubsError(app, "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]")) + + t.Run("acl_config", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/config/description", + "ACL_TABLE", "X_ACL_IPV4", true)) + + t.Run("acl_state", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/state", + "ACL_TABLE", "X_ACL_IPV4", true)) + + t.Run("entries", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/acl-entries", + "ACL_RULE", "X_ACL_IPV4|*", false)) + + t.Run("rule*", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/acl-entries/acl-entry", + "ACL_RULE", "X_ACL_IPV4|*", false)) + + t.Run("rule", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=1]", + "ACL_RULE", "X_ACL_IPV4|RULE_1", false)) + + t.Run("rule_state", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=100]/state", + "ACL_RULE", "X_ACL_IPV4|RULE_100", true)) + + t.Run("rule_sip", testSubs(app, + "/openconfig-acl:acl/acl-sets/acl-set[name=X][type=ACL_IPV4]/acl-entries/acl-entry[sequence-id=200]/ipv4/config/source-address", + "ACL_RULE", "X_ACL_IPV4|RULE_200", true)) + +} + +// testSubs creates a test case which invokes translateSubscribe on an +// app interafce and check returned notificationInfo matches given values. +func testSubs(app appInterface, path, oTable, oKey string, oCache bool) func(*testing.T) { + return func(t *testing.T) { + _, nt, err := app.translateSubscribe([db.MaxDB]*db.DB{}, path) + if err != nil { + t.Fatalf("Unexpected error processing '%s'; err=%v", path, err) + } + if nt == nil || nt.needCache != oCache || nt.table.Name != oTable || + !reflect.DeepEqual(nt.key.Comp, strings.Split(oKey, "|")) { + t.Logf("translateSubscribe for path '%s'", path) + t.Logf("Expected table '%s', key '%v', cache %v", oTable, oKey, oCache) + if nt == nil { + t.Fatalf("Found nil") + } else { + t.Fatalf("Found table '%s', key '%s', cache %v", + nt.table.Name, strings.Join(nt.key.Comp, "|"), nt.needCache) + } + } + } +} + +// testSubsError creates a test case which invokes translateSubscribe on +// an app interafce and expects it to return an error +func testSubsError(app appInterface, path string) func(*testing.T) { + return func(t *testing.T) { + _, _, err := app.translateSubscribe([db.MaxDB]*db.DB{}, path) + if err == nil { + t.Fatalf("Expected error for path '%s'", path) + } + } +} + +/***************************************************************************/ +/////////// JSON Data for Tests /////////////// +/***************************************************************************/ +var emptyJson string = "{}" + +var bulkAclCreateJsonRequest string = "{\"acl-sets\":{\"acl-set\":[{\"name\":\"MyACL1\",\"type\":\"ACL_IPV4\",\"config\":{\"name\":\"MyACL1\",\"type\":\"ACL_IPV4\",\"description\":\"Description for MyACL1\"},\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":1,\"config\":{\"sequence-id\":1,\"description\":\"Description for MyACL1 Rule Seq 1\"},\"ipv4\":{\"config\":{\"source-address\":\"11.1.1.1/32\",\"destination-address\":\"21.1.1.1/32\",\"dscp\":1,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":101,\"destination-port\":201}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}},{\"sequence-id\":2,\"config\":{\"sequence-id\":2,\"description\":\"Description for MyACL1 Rule Seq 2\"},\"ipv4\":{\"config\":{\"source-address\":\"11.1.1.2/32\",\"destination-address\":\"21.1.1.2/32\",\"dscp\":2,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":102,\"destination-port\":202}},\"actions\":{\"config\":{\"forwarding-action\":\"DROP\"}}},{\"sequence-id\":3,\"config\":{\"sequence-id\":3,\"description\":\"Description for MyACL1 Rule Seq 3\"},\"ipv4\":{\"config\":{\"source-address\":\"11.1.1.3/32\",\"destination-address\":\"21.1.1.3/32\",\"dscp\":3,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":103,\"destination-port\":203}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}},{\"sequence-id\":4,\"config\":{\"sequence-id\":4,\"description\":\"Description for MyACL1 Rule Seq 4\"},\"ipv4\":{\"config\":{\"source-address\":\"11.1.1.4/32\",\"destination-address\":\"21.1.1.4/32\",\"dscp\":4,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":104,\"destination-port\":204}},\"actions\":{\"config\":{\"forwarding-action\":\"DROP\"}}},{\"sequence-id\":5,\"config\":{\"sequence-id\":5,\"description\":\"Description for MyACL1 Rule Seq 5\"},\"ipv4\":{\"config\":{\"source-address\":\"11.1.1.5/32\",\"destination-address\":\"21.1.1.5/32\",\"dscp\":5,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":105,\"destination-port\":205}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}}]}},{\"name\":\"MyACL2\",\"type\":\"ACL_IPV4\",\"config\":{\"name\":\"MyACL2\",\"type\":\"ACL_IPV4\",\"description\":\"Description for MyACL2\"},\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":1,\"config\":{\"sequence-id\":1,\"description\":\"Description for Rule Seq 1\"},\"ipv4\":{\"config\":{\"source-address\":\"12.1.1.1/32\",\"destination-address\":\"22.1.1.1/32\",\"dscp\":1,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":101,\"destination-port\":201}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}},{\"sequence-id\":2,\"config\":{\"sequence-id\":2,\"description\":\"Description for Rule Seq 2\"},\"ipv4\":{\"config\":{\"source-address\":\"12.1.1.2/32\",\"destination-address\":\"22.1.1.2/32\",\"dscp\":2,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":102,\"destination-port\":202}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}},{\"sequence-id\":3,\"config\":{\"sequence-id\":3,\"description\":\"Description for Rule Seq 3\"},\"ipv4\":{\"config\":{\"source-address\":\"12.1.1.3/32\",\"destination-address\":\"22.1.1.3/32\",\"dscp\":3,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":103,\"destination-port\":203}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}},{\"sequence-id\":4,\"config\":{\"sequence-id\":4,\"description\":\"Description for Rule Seq 4\"},\"ipv4\":{\"config\":{\"source-address\":\"12.1.1.4/32\",\"destination-address\":\"22.1.1.4/32\",\"dscp\":4,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":104,\"destination-port\":204}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}},{\"sequence-id\":5,\"config\":{\"sequence-id\":5,\"description\":\"Description for Rule Seq 5\"},\"ipv4\":{\"config\":{\"source-address\":\"12.1.1.5/32\",\"destination-address\":\"22.1.1.5/32\",\"dscp\":5,\"protocol\":\"IP_TCP\"}},\"transport\":{\"config\":{\"source-port\":105,\"destination-port\":205}},\"actions\":{\"config\":{\"forwarding-action\":\"ACCEPT\"}}}]}}]},\"interfaces\":{\"interface\":[{\"id\":\"Ethernet0\",\"config\":{\"id\":\"Ethernet0\"},\"interface-ref\":{\"config\":{\"interface\":\"Ethernet0\"}},\"ingress-acl-sets\":{\"ingress-acl-set\":[{\"set-name\":\"MyACL1\",\"type\":\"ACL_IPV4\",\"config\":{\"set-name\":\"MyACL1\",\"type\":\"ACL_IPV4\"}}]}},{\"id\":\"Ethernet4\",\"config\":{\"id\":\"Ethernet4\"},\"interface-ref\":{\"config\":{\"interface\":\"Ethernet4\"}},\"ingress-acl-sets\":{\"ingress-acl-set\":[{\"set-name\":\"MyACL2\",\"type\":\"ACL_IPV4\",\"config\":{\"set-name\":\"MyACL2\",\"type\":\"ACL_IPV4\"}}]}}]}}" + +var bulkAclCreateJsonResponse string = "{\"openconfig-acl:acl\":{\"acl-sets\":{\"acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":1},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.1/32\",\"dscp\":1,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.1/32\"},\"state\":{\"destination-address\":\"21.1.1.1/32\",\"dscp\":1,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.1/32\"}},\"sequence-id\":1,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":1},\"transport\":{\"config\":{\"destination-port\":201,\"source-port\":101},\"state\":{\"destination-port\":201,\"source-port\":101}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:DROP\"},\"state\":{\"forwarding-action\":\"openconfig-acl:DROP\"}},\"config\":{\"sequence-id\":2},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.2/32\",\"dscp\":2,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.2/32\"},\"state\":{\"destination-address\":\"21.1.1.2/32\",\"dscp\":2,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.2/32\"}},\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2},\"transport\":{\"config\":{\"destination-port\":202,\"source-port\":102},\"state\":{\"destination-port\":202,\"source-port\":102}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":3},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.3/32\",\"dscp\":3,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.3/32\"},\"state\":{\"destination-address\":\"21.1.1.3/32\",\"dscp\":3,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.3/32\"}},\"sequence-id\":3,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":3},\"transport\":{\"config\":{\"destination-port\":203,\"source-port\":103},\"state\":{\"destination-port\":203,\"source-port\":103}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:DROP\"},\"state\":{\"forwarding-action\":\"openconfig-acl:DROP\"}},\"config\":{\"sequence-id\":4},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.4/32\",\"dscp\":4,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.4/32\"},\"state\":{\"destination-address\":\"21.1.1.4/32\",\"dscp\":4,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.4/32\"}},\"sequence-id\":4,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":4},\"transport\":{\"config\":{\"destination-port\":204,\"source-port\":104},\"state\":{\"destination-port\":204,\"source-port\":104}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":5},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.5/32\",\"dscp\":5,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.5/32\"},\"state\":{\"destination-address\":\"21.1.1.5/32\",\"dscp\":5,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.5/32\"}},\"sequence-id\":5,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":5},\"transport\":{\"config\":{\"destination-port\":205,\"source-port\":105},\"state\":{\"destination-port\":205,\"source-port\":105}}}]},\"config\":{\"description\":\"Description for MyACL1\",\"name\":\"MyACL1\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"name\":\"MyACL1\",\"state\":{\"description\":\"Description for MyACL1\",\"name\":\"MyACL1\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"},{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":1},\"ipv4\":{\"config\":{\"destination-address\":\"22.1.1.1/32\",\"dscp\":1,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.1/32\"},\"state\":{\"destination-address\":\"22.1.1.1/32\",\"dscp\":1,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.1/32\"}},\"sequence-id\":1,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":1},\"transport\":{\"config\":{\"destination-port\":201,\"source-port\":101},\"state\":{\"destination-port\":201,\"source-port\":101}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":2},\"ipv4\":{\"config\":{\"destination-address\":\"22.1.1.2/32\",\"dscp\":2,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.2/32\"},\"state\":{\"destination-address\":\"22.1.1.2/32\",\"dscp\":2,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.2/32\"}},\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2},\"transport\":{\"config\":{\"destination-port\":202,\"source-port\":102},\"state\":{\"destination-port\":202,\"source-port\":102}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":3},\"ipv4\":{\"config\":{\"destination-address\":\"22.1.1.3/32\",\"dscp\":3,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.3/32\"},\"state\":{\"destination-address\":\"22.1.1.3/32\",\"dscp\":3,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.3/32\"}},\"sequence-id\":3,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":3},\"transport\":{\"config\":{\"destination-port\":203,\"source-port\":103},\"state\":{\"destination-port\":203,\"source-port\":103}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":4},\"ipv4\":{\"config\":{\"destination-address\":\"22.1.1.4/32\",\"dscp\":4,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.4/32\"},\"state\":{\"destination-address\":\"22.1.1.4/32\",\"dscp\":4,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.4/32\"}},\"sequence-id\":4,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":4},\"transport\":{\"config\":{\"destination-port\":204,\"source-port\":104},\"state\":{\"destination-port\":204,\"source-port\":104}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":5},\"ipv4\":{\"config\":{\"destination-address\":\"22.1.1.5/32\",\"dscp\":5,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.5/32\"},\"state\":{\"destination-address\":\"22.1.1.5/32\",\"dscp\":5,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"12.1.1.5/32\"}},\"sequence-id\":5,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":5},\"transport\":{\"config\":{\"destination-port\":205,\"source-port\":105},\"state\":{\"destination-port\":205,\"source-port\":105}}}]},\"config\":{\"description\":\"Description for MyACL2\",\"name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"name\":\"MyACL2\",\"state\":{\"description\":\"Description for MyACL2\",\"name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"interfaces\":{\"interface\":[{\"config\":{\"id\":\"Ethernet0\"},\"id\":\"Ethernet0\",\"ingress-acl-sets\":{\"ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":1,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":1}},{\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2}},{\"sequence-id\":3,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":3}},{\"sequence-id\":4,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":4}},{\"sequence-id\":5,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":5}}]},\"config\":{\"set-name\":\"MyACL1\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL1\",\"state\":{\"set-name\":\"MyACL1\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"state\":{\"id\":\"Ethernet0\"}},{\"config\":{\"id\":\"Ethernet4\"},\"id\":\"Ethernet4\",\"ingress-acl-sets\":{\"ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":1,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":1}},{\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2}},{\"sequence-id\":3,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":3}},{\"sequence-id\":4,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":4}},{\"sequence-id\":5,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":5}}]},\"config\":{\"set-name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL2\",\"state\":{\"set-name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"state\":{\"id\":\"Ethernet4\"}}]}}}" + +var oneAclCreateWithRulesJsonRequest string = "{ \"name\": \"MyACL3\", \"type\": \"ACL_IPV4\", \"config\": { \"name\": \"MyACL3\", \"type\": \"ACL_IPV4\", \"description\": \"Description for MyACL3\" }, \"acl-entries\": { \"acl-entry\": [ { \"sequence-id\": 1, \"config\": { \"sequence-id\": 1, \"description\": \"Description for MyACL3 Rule Seq 1\" }, \"ipv4\": { \"config\": { \"source-address\": \"11.1.1.1/32\", \"destination-address\": \"21.1.1.1/32\", \"dscp\": 1, \"protocol\": \"IP_TCP\" } }, \"transport\": { \"config\": { \"source-port\": 101, \"destination-port\": 201 } }, \"actions\": { \"config\": { \"forwarding-action\": \"ACCEPT\" } } }, { \"sequence-id\": 2, \"config\": { \"sequence-id\": 2, \"description\": \"Description for MyACL3 Rule Seq 2\" }, \"ipv4\": { \"config\": { \"source-address\": \"11.1.1.2/32\", \"destination-address\": \"21.1.1.2/32\", \"dscp\": 2, \"protocol\": \"IP_UDP\" } }, \"transport\": { \"config\": { \"source-port\": 102, \"destination-port\": 202 } }, \"actions\": { \"config\": { \"forwarding-action\": \"DROP\" } } }, { \"sequence-id\": 3, \"config\": { \"sequence-id\": 3, \"description\": \"Description for MyACL3 Rule Seq 3\" }, \"ipv4\": { \"config\": { \"source-address\": \"11.1.1.3/32\", \"destination-address\": \"21.1.1.3/32\", \"dscp\": 3, \"protocol\": \"IP_TCP\" } }, \"transport\": { \"config\": { \"source-port\": 103, \"destination-port\": 203 } }, \"actions\": { \"config\": { \"forwarding-action\": \"ACCEPT\" } } }, { \"sequence-id\": 4, \"config\": { \"sequence-id\": 4, \"description\": \"Description for MyACL3 Rule Seq 4\" }, \"ipv4\": { \"config\": { \"source-address\": \"11.1.1.4/32\", \"destination-address\": \"21.1.1.4/32\", \"dscp\": 4, \"protocol\": \"IP_TCP\" } }, \"transport\": { \"config\": { \"source-port\": 104, \"destination-port\": 204 } }, \"actions\": { \"config\": { \"forwarding-action\": \"DROP\" } } }, { \"sequence-id\": 5, \"config\": { \"sequence-id\": 5, \"description\": \"Description for MyACL3 Rule Seq 5\" }, \"ipv4\": { \"config\": { \"source-address\": \"11.1.1.5/32\", \"destination-address\": \"21.1.1.5/32\", \"dscp\": 5, \"protocol\": \"IP_TCP\" } }, \"transport\": { \"config\": { \"source-port\": 105, \"destination-port\": 205 } }, \"actions\": { \"config\": { \"forwarding-action\": \"ACCEPT\" } } } ] }}" + +var oneAclCreateWithRulesJsonResponse string = "{\"openconfig-acl:acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":1},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.1/32\",\"dscp\":1,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.1/32\"},\"state\":{\"destination-address\":\"21.1.1.1/32\",\"dscp\":1,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.1/32\"}},\"sequence-id\":1,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":1},\"transport\":{\"config\":{\"destination-port\":201,\"source-port\":101},\"state\":{\"destination-port\":201,\"source-port\":101}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:DROP\"},\"state\":{\"forwarding-action\":\"openconfig-acl:DROP\"}},\"config\":{\"sequence-id\":2},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.2/32\",\"dscp\":2,\"protocol\":\"openconfig-packet-match-types:IP_UDP\",\"source-address\":\"11.1.1.2/32\"},\"state\":{\"destination-address\":\"21.1.1.2/32\",\"dscp\":2,\"protocol\":\"openconfig-packet-match-types:IP_UDP\",\"source-address\":\"11.1.1.2/32\"}},\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2},\"transport\":{\"config\":{\"destination-port\":202,\"source-port\":102},\"state\":{\"destination-port\":202,\"source-port\":102}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":3},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.3/32\",\"dscp\":3,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.3/32\"},\"state\":{\"destination-address\":\"21.1.1.3/32\",\"dscp\":3,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.3/32\"}},\"sequence-id\":3,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":3},\"transport\":{\"config\":{\"destination-port\":203,\"source-port\":103},\"state\":{\"destination-port\":203,\"source-port\":103}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:DROP\"},\"state\":{\"forwarding-action\":\"openconfig-acl:DROP\"}},\"config\":{\"sequence-id\":4},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.4/32\",\"dscp\":4,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.4/32\"},\"state\":{\"destination-address\":\"21.1.1.4/32\",\"dscp\":4,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.4/32\"}},\"sequence-id\":4,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":4},\"transport\":{\"config\":{\"destination-port\":204,\"source-port\":104},\"state\":{\"destination-port\":204,\"source-port\":104}}},{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":5},\"ipv4\":{\"config\":{\"destination-address\":\"21.1.1.5/32\",\"dscp\":5,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.5/32\"},\"state\":{\"destination-address\":\"21.1.1.5/32\",\"dscp\":5,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11.1.1.5/32\"}},\"sequence-id\":5,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":5},\"transport\":{\"config\":{\"destination-port\":205,\"source-port\":105},\"state\":{\"destination-port\":205,\"source-port\":105}}}]},\"config\":{\"description\":\"Description for MyACL3\",\"name\":\"MyACL3\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"name\":\"MyACL3\",\"state\":{\"description\":\"Description for MyACL3\",\"name\":\"MyACL3\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]}" + +var oneAclCreateJsonRequest string = "{\"config\": {\"name\": \"MyACL5\",\"type\": \"ACL_IPV4\",\"description\": \"Description for MyACL5\"}}" +var oneAclCreateJsonResponse string = "{\"openconfig-acl:acl-set\":[{\"config\":{\"description\":\"Description for MyACL5\",\"name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"name\":\"MyACL5\",\"state\":{\"description\":\"Description for MyACL5\",\"name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]}" + +var aclDescrUpdateJson string = "{\"openconfig-acl:description\":\"Verifying ACL Description Update\"}" + +var requestOneRulePostJson string = "{\"sequence-id\": 8,\"config\": {\"sequence-id\": 8,\"description\": \"Description for MyACL5 Rule Seq 8\"},\"ipv4\": {\"config\": {\"source-address\": \"4.4.4.4/24\",\"destination-address\": \"5.5.5.5/24\",\"protocol\": \"IP_TCP\"}},\"transport\": {\"config\": {\"source-port\": 101,\"destination-port\": 100,\"tcp-flags\": [\"TCP_FIN\",\"TCP_ACK\"]}},\"actions\": {\"config\": {\"forwarding-action\": \"ACCEPT\"}}}" + +var requestOneRulePatchJson string = "{\"sequence-id\": 8,\"config\": {\"sequence-id\": 8,\"description\": \"Description for MyACL5 Rule Seq 8\"},\"ipv4\": {\"config\": {\"source-address\": \"4.8.4.8/24\",\"destination-address\": \"15.5.15.5/24\",\"protocol\": \"IP_L2TP\"}},\"transport\": {\"config\": {\"source-port\": 101,\"destination-port\": 100,\"tcp-flags\": [\"TCP_FIN\",\"TCP_ACK\",\"TCP_RST\",\"TCP_ECE\"]}},\"actions\": {\"config\": {\"forwarding-action\": \"ACCEPT\"}}}" + +var responseOneRuleJson string = "{\"openconfig-acl:acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":8},\"ipv4\":{\"config\":{\"destination-address\":\"5.5.5.5/24\",\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"4.4.4.4/24\"},\"state\":{\"destination-address\":\"5.5.5.5/24\",\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"4.4.4.4/24\"}},\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]}}}]}" + +var responseOneRulePatchJson string = "{\"openconfig-acl:acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":8},\"ipv4\":{\"config\":{\"destination-address\":\"15.5.15.5/24\",\"protocol\":\"openconfig-packet-match-types:IP_L2TP\",\"source-address\":\"4.8.4.8/24\"},\"state\":{\"destination-address\":\"15.5.15.5/24\",\"protocol\":\"openconfig-packet-match-types:IP_L2TP\",\"source-address\":\"4.8.4.8/24\"}},\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_RST\",\"openconfig-packet-match-types:TCP_ACK\",\"openconfig-packet-match-types:TCP_ECE\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_RST\",\"openconfig-packet-match-types:TCP_ACK\",\"openconfig-packet-match-types:TCP_ECE\"]}}}]}" + +var emptyAclDescriptionJson string = "{\"openconfig-acl:description\":\"\"}" +var emptyRuleDscpJson string = "{\"openconfig-acl:dscp\":0}" + +var ingressAclSetCreateJsonRequest string = "{ \"openconfig-acl:config\": { \"set-name\": \"MyACL5\", \"type\": \"ACL_IPV4\" }}" +var ingressAclSetCreateJsonResponse string = "{\"openconfig-acl:ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]},\"config\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL5\",\"state\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]}" + +var egressAclSetCreateJsonResponse string = "{\"openconfig-acl:egress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]},\"config\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL5\",\"state\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]}" + +var replaceMultiRulesWithOneRuleJsonRequest string = "{\"name\": \"MyACL3\",\"type\": \"ACL_IPV4\",\"config\": {\"name\": \"MyACL3\",\"type\": \"ACL_IPV4\",\"description\": \"Description for MyACL3\"},\"acl-entries\": {\"acl-entry\": [{\"sequence-id\": 8,\"config\": {\"sequence-id\": 8,\"description\": \"Description for MyACL3 Rule Seq 8\"},\"ipv4\": {\"config\": {\"source-address\": \"81.1.1.1/32\",\"destination-address\": \"91.1.1.1/32\",\"protocol\": \"IP_TCP\"}},\"transport\": {\"config\": {\"source-port\": \"801..811\",\"destination-port\": \"901..921\"}},\"actions\": {\"config\": {\"forwarding-action\": \"REJECT\"}}}]}}" + +var replaceMultiRulesWithOneRuleJsonResponse string = "{\"openconfig-acl:acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:DROP\"},\"state\":{\"forwarding-action\":\"openconfig-acl:DROP\"}},\"config\":{\"sequence-id\":8},\"ipv4\":{\"config\":{\"destination-address\":\"91.1.1.1/32\",\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"81.1.1.1/32\"},\"state\":{\"destination-address\":\"91.1.1.1/32\",\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"81.1.1.1/32\"}},\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8},\"transport\":{\"config\":{\"destination-port\":\"901-921\",\"source-port\":\"801-811\"},\"state\":{\"destination-port\":\"901-921\",\"source-port\":\"801-811\"}}}]},\"config\":{\"description\":\"Description for MyACL3\",\"name\":\"MyACL3\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"name\":\"MyACL3\",\"state\":{\"description\":\"Description for MyACL3\",\"name\":\"MyACL3\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]}" + +var getFromAclSetsTreeLevelResponse string = "{\"openconfig-acl:acl-sets\":{\"acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":8},\"ipv4\":{\"config\":{\"destination-address\":\"5.5.5.5/24\",\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"4.4.4.4/24\"},\"state\":{\"destination-address\":\"5.5.5.5/24\",\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"4.4.4.4/24\"}},\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]}}}]},\"config\":{\"description\":\"Description for MyACL5\",\"name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"name\":\"MyACL5\",\"state\":{\"description\":\"Description for MyACL5\",\"name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]}}" + +var getAllPortsFromInterfacesTreeLevelResponse string = "{\"openconfig-acl:interfaces\":{\"interface\":[{\"config\":{\"id\":\"Ethernet4\"},\"egress-acl-sets\":{\"egress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]},\"config\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL5\",\"state\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"id\":\"Ethernet4\",\"state\":{\"id\":\"Ethernet4\"}}]}}" + +var getPortBindingFromInterfaceTreeLevelResponse string = "{\"openconfig-acl:interface\":[{\"config\":{\"id\":\"Ethernet4\"},\"egress-acl-sets\":{\"egress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]},\"config\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL5\",\"state\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"id\":\"Ethernet4\",\"state\":{\"id\":\"Ethernet4\"}}]}" + +var getBindingAclEntryResponse string = "{\"openconfig-acl:acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]}" + +var getMultiportBindingOnSingleAclResponse string = "{\"openconfig-acl:interfaces\":{\"interface\":[{\"config\":{\"id\":\"Ethernet0\"},\"egress-acl-sets\":{\"egress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]},\"config\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL5\",\"state\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"id\":\"Ethernet0\",\"state\":{\"id\":\"Ethernet0\"}},{\"config\":{\"id\":\"Ethernet4\"},\"egress-acl-sets\":{\"egress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":8,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":8}}]},\"config\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"set-name\":\"MyACL5\",\"state\":{\"set-name\":\"MyACL5\",\"type\":\"openconfig-acl:ACL_IPV4\"},\"type\":\"openconfig-acl:ACL_IPV4\"}]},\"id\":\"Ethernet4\",\"state\":{\"id\":\"Ethernet4\"}}]}}" + +var oneIPv6AclCreateJsonRequest string = "{\"config\": {\"name\": \"MyACL6\",\"type\": \"ACL_IPV6\",\"description\": \"Description for IPv6 ACL MyACL6\"}}" +var oneIPv6AclCreateJsonResponse string = "{\"openconfig-acl:acl-set\":[{\"config\":{\"description\":\"Description for IPv6 ACL MyACL6\",\"name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"name\":\"MyACL6\",\"state\":{\"description\":\"Description for IPv6 ACL MyACL6\",\"name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"type\":\"openconfig-acl:ACL_IPV6\"}]}" + +var oneIPv6RuleCreateJsonRequest string = "{\"sequence-id\": 6,\"config\": {\"sequence-id\": 6,\"description\": \"Description for MyACL6 Rule Seq 6\"},\"ipv6\": {\"config\": {\"source-address\": \"11::67/64\",\"destination-address\": \"22::87/64\",\"protocol\": \"IP_TCP\",\"dscp\": 11}},\"transport\": {\"config\": {\"source-port\": 101,\"destination-port\": 100,\"tcp-flags\": [\"TCP_FIN\",\"TCP_ACK\"]}},\"actions\": {\"config\": {\"forwarding-action\": \"ACCEPT\"}}}" +var oneIPv6RuleCreateJsonResponse string = "{\"openconfig-acl:acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":6},\"ipv6\":{\"config\":{\"destination-address\":\"22::87/64\",\"dscp\":11,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11::67/64\"},\"state\":{\"destination-address\":\"22::87/64\",\"dscp\":11,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11::67/64\"}},\"sequence-id\":6,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":6},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]}}}]}" + +var ingressIPv6AclSetCreateJsonRequest string = "{ \"openconfig-acl:config\": { \"set-name\": \"MyACL6\", \"type\": \"ACL_IPV6\" }}" +var ingressIPv6AclSetCreateJsonResponse string = "{\"openconfig-acl:ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":6,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":6}}]},\"config\":{\"set-name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"set-name\":\"MyACL6\",\"state\":{\"set-name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"type\":\"openconfig-acl:ACL_IPV6\"}]}" + +var getIPv6AclsFromAclSetListLevelResponse string = "{\"openconfig-acl:acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":6},\"ipv6\":{\"config\":{\"destination-address\":\"22::87/64\",\"dscp\":11,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11::67/64\"},\"state\":{\"destination-address\":\"22::87/64\",\"dscp\":11,\"protocol\":\"openconfig-packet-match-types:IP_TCP\",\"source-address\":\"11::67/64\"}},\"sequence-id\":6,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":6},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]}}}]},\"config\":{\"description\":\"Description for IPv6 ACL MyACL6\",\"name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"name\":\"MyACL6\",\"state\":{\"description\":\"Description for IPv6 ACL MyACL6\",\"name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"type\":\"openconfig-acl:ACL_IPV6\"}]}" + +var getIPv6AllPortsBindingsResponse string = "{\"openconfig-acl:interfaces\":{\"interface\":[{\"config\":{\"id\":\"Ethernet4\"},\"id\":\"Ethernet4\",\"ingress-acl-sets\":{\"ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":6,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":6}}]},\"config\":{\"set-name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"set-name\":\"MyACL6\",\"state\":{\"set-name\":\"MyACL6\",\"type\":\"openconfig-acl:ACL_IPV6\"},\"type\":\"openconfig-acl:ACL_IPV6\"}]},\"state\":{\"id\":\"Ethernet4\"}}]}}" + +var oneL2AclCreateJsonRequest string = "{\"config\": {\"name\": \"MyACL2\",\"type\": \"ACL_L2\",\"description\": \"Description for L2 ACL MyACL2\"}}" +var oneL2AclCreateJsonResponse string = "{\"openconfig-acl:acl-set\":[{\"config\":{\"description\":\"Description for L2 ACL MyACL2\",\"name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"name\":\"MyACL2\",\"state\":{\"description\":\"Description for L2 ACL MyACL2\",\"name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"type\":\"openconfig-acl:ACL_L2\"}]}" + +var oneL2RuleCreateJsonRequest string = "{\"sequence-id\": 2,\"config\": {\"sequence-id\": 2,\"description\": \"Description for MyACL2 Rule Seq 2\"},\"l2\": {\"config\": {\"ethertype\": \"ETHERTYPE_VLAN\"}},\"transport\": {\"config\": {\"source-port\": 101,\"destination-port\": 100,\"tcp-flags\": [\"TCP_FIN\",\"TCP_ACK\"]}},\"actions\": {\"config\": {\"forwarding-action\": \"ACCEPT\"}}}" + +var oneL2RuleCreateJsonResponse string = "{\"openconfig-acl:acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":2},\"l2\":{\"config\":{\"ethertype\":\"openconfig-packet-match-types:ETHERTYPE_VLAN\"},\"state\":{\"ethertype\":\"openconfig-packet-match-types:ETHERTYPE_VLAN\"}},\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]}}}]}" + +var ingressL2AclSetCreateJsonRequest string = "{ \"openconfig-acl:config\": { \"set-name\": \"MyACL2\", \"type\": \"ACL_L2\" }}" +var ingressL2AclSetCreateJsonResponse string = "{\"openconfig-acl:ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2}}]},\"config\":{\"set-name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"set-name\":\"MyACL2\",\"state\":{\"set-name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"type\":\"openconfig-acl:ACL_L2\"}]}" + +var getL2AclsFromAclSetListLevelResponse string = "{\"openconfig-acl:acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"actions\":{\"config\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"},\"state\":{\"forwarding-action\":\"openconfig-acl:ACCEPT\"}},\"config\":{\"sequence-id\":2},\"l2\":{\"config\":{\"ethertype\":\"openconfig-packet-match-types:ETHERTYPE_VLAN\"},\"state\":{\"ethertype\":\"openconfig-packet-match-types:ETHERTYPE_VLAN\"}},\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2},\"transport\":{\"config\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]},\"state\":{\"destination-port\":100,\"source-port\":101,\"tcp-flags\":[\"openconfig-packet-match-types:TCP_FIN\",\"openconfig-packet-match-types:TCP_ACK\"]}}}]},\"config\":{\"description\":\"Description for L2 ACL MyACL2\",\"name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"name\":\"MyACL2\",\"state\":{\"description\":\"Description for L2 ACL MyACL2\",\"name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"type\":\"openconfig-acl:ACL_L2\"}]}" + +var getL2AllPortsBindingsResponse string = "{\"openconfig-acl:interfaces\":{\"interface\":[{\"config\":{\"id\":\"Ethernet0\"},\"id\":\"Ethernet0\",\"ingress-acl-sets\":{\"ingress-acl-set\":[{\"acl-entries\":{\"acl-entry\":[{\"sequence-id\":2,\"state\":{\"matched-octets\":\"0\",\"matched-packets\":\"0\",\"sequence-id\":2}}]},\"config\":{\"set-name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"set-name\":\"MyACL2\",\"state\":{\"set-name\":\"MyACL2\",\"type\":\"openconfig-acl:ACL_L2\"},\"type\":\"openconfig-acl:ACL_L2\"}]},\"state\":{\"id\":\"Ethernet0\"}}]}}" + +var aclCreateWithInvalidInterfaceBinding string = "{ \"acl-sets\": { \"acl-set\": [ { \"name\": \"MyACL1\", \"type\": \"ACL_IPV4\", \"config\": { \"name\": \"MyACL1\", \"type\": \"ACL_IPV4\", \"description\": \"Description for MyACL1\" }, \"acl-entries\": { \"acl-entry\": [ { \"sequence-id\": 1, \"config\": { \"sequence-id\": 1, \"description\": \"Description for MyACL1 Rule Seq 1\" }, \"ipv4\": { \"config\": { \"source-address\": \"11.1.1.1/32\", \"destination-address\": \"21.1.1.1/32\", \"dscp\": 1, \"protocol\": \"IP_TCP\" } }, \"transport\": { \"config\": { \"source-port\": 101, \"destination-port\": 201 } }, \"actions\": { \"config\": { \"forwarding-action\": \"ACCEPT\" } } } ] } } ] }, \"interfaces\": { \"interface\": [ { \"id\": \"Ethernet2112\", \"config\": { \"id\": \"Ethernet2112\" }, \"interface-ref\": { \"config\": { \"interface\": \"Ethernet2112\" } }, \"ingress-acl-sets\": { \"ingress-acl-set\": [ { \"set-name\": \"MyACL1\", \"type\": \"ACL_IPV4\", \"config\": { \"set-name\": \"MyACL1\", \"type\": \"ACL_IPV4\" } } ] } } ] }}" + +var requestOneDuplicateRulePostJson string = "{\"sequence-id\": 1,\"config\": {\"sequence-id\": 1,\"description\": \"Description for MyACL3 Rule Seq 1\"},\"ipv4\": {\"config\": {\"source-address\": \"4.4.4.4/24\",\"destination-address\": \"5.5.5.5/24\",\"protocol\": \"IP_TCP\"}},\"transport\": {\"config\": {\"source-port\": 101,\"destination-port\": 100,\"tcp-flags\": [\"TCP_FIN\",\"TCP_ACK\"]}},\"actions\": {\"config\": {\"forwarding-action\": \"ACCEPT\"}}}" diff --git a/translib/app_interface.go b/translib/app_interface.go new file mode 100644 index 000000000..3874937fb --- /dev/null +++ b/translib/app_interface.go @@ -0,0 +1,170 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +/* +Package translib defines the interface for all the app modules + +It exposes register function for all the app modules to register + +It stores all the app module information in a map and presents it + +to the tranlib infra when it asks for the same. +*/ + +package translib + +import ( + "errors" + "reflect" + "strings" + "github.com/Azure/sonic-mgmt-common/translib/db" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" +) + +//Structure containing app module information +type appInfo struct { + appType reflect.Type + ygotRootType reflect.Type + isNative bool + tablesToWatch []*db.TableSpec +} + +//Structure containing the app data coming from translib infra +type appData struct { + path string + payload []byte + ygotRoot *ygot.GoStruct + ygotTarget *interface{} +} + +//map containing the base path to app module info +var appMap map[string]*appInfo + +//array containing all the supported models +var models []ModelData + +//Interface for all App Modules +type appInterface interface { + initialize(data appData) + translateCreate(d *db.DB) ([]db.WatchKeys, error) + translateUpdate(d *db.DB) ([]db.WatchKeys, error) + translateReplace(d *db.DB) ([]db.WatchKeys, error) + translateDelete(d *db.DB) ([]db.WatchKeys, error) + translateGet(dbs [db.MaxDB]*db.DB) error + translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) + processCreate(d *db.DB) (SetResponse, error) + processUpdate(d *db.DB) (SetResponse, error) + processReplace(d *db.DB) (SetResponse, error) + processDelete(d *db.DB) (SetResponse, error) + processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) +} + +//App modules will use this function to register with App interface during boot up +func register(path string, info *appInfo) error { + var err error + log.Info("Registering for path =", path) + + if appMap == nil { + appMap = make(map[string]*appInfo) + } + + if _, ok := appMap[path]; ok == false { + + appMap[path] = info + + } else { + log.Fatal("Duplicate path being registered. Path =", path) + err = errors.New("Duplicate path") + } + + return err +} + +//Adds the model information to the supported models array +func addModel(model *ModelData) error { + var err error + + models = append(models, *model) + + //log.Info("Models = ", models) + return err +} + +//App modules can use this function to unregister itself from the app interface +func unregister(path string) error { + var err error + log.Info("Unregister for path =", path) + + _, ok := appMap[path] + + if ok { + log.Info("deleting for path =", path) + delete(appMap, path) + } + + return err +} + +//Translib infra will use this function get the app info for a given path +func getAppModuleInfo(path string) (*appInfo, error) { + var err error + log.Info("getAppModule called for path =", path) + + for pattern, app := range appMap { + if !strings.HasPrefix(path, pattern) { + continue + } + + log.Info("found the entry in the map for path =", pattern) + + return app, err + } + + errStr := "Unsupported path=" + path + + err = errors.New(errStr) + log.Error(errStr) + + var app *appInfo + + return app, err +} + +//Get all the supported models +func getModels() []ModelData { + + return models +} + +//Creates a new app from the appType and returns it as an appInterface +func getAppInterface(appType reflect.Type) (appInterface, error) { + var err error + appInstance := reflect.New(appType) + app, ok := appInstance.Interface().(appInterface) + + if !ok { + err = errors.New("Invalid appType") + log.Fatal("Appmodule does not confirm to appInterface method conventions for appType=", appType) + } else { + log.Info("cast to appInterface worked", app) + } + + return app, err +} diff --git a/translib/app_utils.go b/translib/app_utils.go new file mode 100644 index 000000000..0ab515abf --- /dev/null +++ b/translib/app_utils.go @@ -0,0 +1,231 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "bytes" + "encoding/json" + "reflect" + "strings" + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" + + log "github.com/golang/glog" + "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/ygot" + "github.com/openconfig/ygot/ytypes" +) + +func getYangPathFromUri(uri string) (string, error) { + var path *gnmi.Path + var err error + + path, err = ygot.StringToPath(uri, ygot.StructuredPath, ygot.StringSlicePath) + if err != nil { + log.Errorf("Error in uri to path conversion: %v", err) + return "", err + } + + yangPath, yperr := ygot.PathToSchemaPath(path) + if yperr != nil { + log.Errorf("Error in Gnmi path to Yang path conversion: %v", yperr) + return "", yperr + } + + return yangPath, err +} + +func getYangPathFromYgotStruct(s ygot.GoStruct, yangPathPrefix string, appModuleName string) string { + tn := reflect.TypeOf(s).Elem().Name() + schema, ok := ocbinds.SchemaTree[tn] + if !ok { + log.Errorf("could not find schema for type %s", tn) + return "" + } else if schema != nil { + yPath := schema.Path() + //yPath = strings.Replace(yPath, "/device/acl", "/openconfig-acl:acl", 1) + yPath = strings.Replace(yPath, yangPathPrefix, appModuleName, 1) + return yPath + } + return "" +} + +func generateGetResponsePayload(targetUri string, deviceObj *ocbinds.Device, ygotTarget *interface{}) ([]byte, error) { + var err error + var payload []byte + + if len(targetUri) == 0 { + return payload, tlerr.InvalidArgs("GetResponse failed as target Uri is not valid") + } + path, err := ygot.StringToPath(targetUri, ygot.StructuredPath, ygot.StringSlicePath) + if err != nil { + return payload, tlerr.InvalidArgs("URI to path conversion failed: %v", err) + } + + // Get current node (corresponds to ygotTarget) and its parent node + var pathList []*gnmi.PathElem = path.Elem + parentPath := &gnmi.Path{} + for i := 0; i < len(pathList); i++ { + if log.V(3) { + log.Infof("pathList[%d]: %s\n", i, pathList[i]) + } + pathSlice := strings.Split(pathList[i].Name, ":") + pathList[i].Name = pathSlice[len(pathSlice)-1] + if i < (len(pathList) - 1) { + parentPath.Elem = append(parentPath.Elem, pathList[i]) + } + } + parentNodeList, err := ytypes.GetNode(ygSchema.RootSchema(), deviceObj, parentPath) + if err != nil { + return payload, err + } + if len(parentNodeList) == 0 { + return payload, tlerr.InvalidArgs("Invalid URI: %s", targetUri) + } + parentNode := parentNodeList[0].Data + + currentNodeList, err := ytypes.GetNode(ygSchema.RootSchema(), deviceObj, path, &(ytypes.GetPartialKeyMatch{})) + if err != nil { + return payload, err + } + if len(currentNodeList) == 0 { + return payload, tlerr.NotFound("Resource not found") + } + //currentNode := currentNodeList[0].Data + currentNodeYangName := currentNodeList[0].Schema.Name + + // Create empty clone of parent node + parentNodeClone := reflect.New(reflect.TypeOf(parentNode).Elem()) + var parentCloneObj ygot.ValidatedGoStruct + var ok bool + if parentCloneObj, ok = (parentNodeClone.Interface()).(ygot.ValidatedGoStruct); ok { + ygot.BuildEmptyTree(parentCloneObj) + pcType := reflect.TypeOf(parentCloneObj).Elem() + pcValue := reflect.ValueOf(parentCloneObj).Elem() + + var currentNodeOCFieldName string + for i := 0; i < pcValue.NumField(); i++ { + fld := pcValue.Field(i) + fldType := pcType.Field(i) + if fldType.Tag.Get("path") == currentNodeYangName { + currentNodeOCFieldName = fldType.Name + // Take value from original parent and set in parent clone + valueFromParent := reflect.ValueOf(parentNode).Elem().FieldByName(currentNodeOCFieldName) + fld.Set(valueFromParent) + break + } + } + if log.V(3) { + log.Infof("Target yang name: %s OC Field name: %s\n", currentNodeYangName, currentNodeOCFieldName) + } + } + + payload, err = dumpIetfJson(parentCloneObj, true) + + return payload, err +} + +func getTargetNodeYangSchema(targetUri string, deviceObj *ocbinds.Device) (*yang.Entry, error) { + if len(targetUri) == 0 { + return nil, tlerr.InvalidArgs("GetResponse failed as target Uri is not valid") + } + path, err := ygot.StringToPath(targetUri, ygot.StructuredPath, ygot.StringSlicePath) + if err != nil { + return nil, tlerr.InvalidArgs("URI to path conversion failed: %v", err) + } + // Get current node (corresponds to ygotTarget) + var pathList []*gnmi.PathElem = path.Elem + for i := 0; i < len(pathList); i++ { + if log.V(3) { + log.Infof("pathList[%d]: %s\n", i, pathList[i]) + } + pathSlice := strings.Split(pathList[i].Name, ":") + pathList[i].Name = pathSlice[len(pathSlice)-1] + } + targetNodeList, err := ytypes.GetNode(ygSchema.RootSchema(), deviceObj, path, &(ytypes.GetPartialKeyMatch{})) + if err != nil { + return nil, tlerr.InvalidArgs("Getting node information failed: %v", err) + } + if len(targetNodeList) == 0 { + return nil, tlerr.NotFound("Resource not found") + } + targetNodeSchema := targetNodeList[0].Schema + //targetNode := targetNodeList[0].Data + if log.V(3) { + log.Infof("Target node yang name: %s\n", targetNodeSchema.Name) + } + return targetNodeSchema, nil +} + +func dumpIetfJson(s ygot.ValidatedGoStruct, skipValidation bool) ([]byte, error) { + jsonStr, err := ygot.EmitJSON(s, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + Indent: " ", + SkipValidation: skipValidation, + RFC7951Config: &ygot.RFC7951JSONConfig{ + AppendModuleName: true, + }, + }) + var buf bytes.Buffer + json.Compact(&buf, []byte(jsonStr)) + return []byte(buf.String()), err +} + +func contains(sl []string, str string) bool { + for _, v := range sl { + if v == str { + return true + } + } + return false +} + +func removeElement(sl []string, str string) []string { + for i := 0; i < len(sl); i++ { + if sl[i] == str { + sl = append(sl[:i], sl[i+1:]...) + i-- + sl = sl[:len(sl)] + break + } + } + return sl +} + +// isNotFoundError return true if the error is a 'not found' error +func isNotFoundError(err error) bool { + switch err.(type) { + case tlerr.TranslibRedisClientEntryNotExist, tlerr.NotFoundError: + return true + default: + return false + } +} + +// asKey cretaes a db.Key from given key components +func asKey(parts ...string) db.Key { + return db.Key{Comp: parts} +} + +func createEmptyDbValue(fieldName string) db.Value { + return db.Value{Field: map[string]string{fieldName: ""}} +} diff --git a/translib/common_app.go b/translib/common_app.go new file mode 100644 index 000000000..ffaa25008 --- /dev/null +++ b/translib/common_app.go @@ -0,0 +1,485 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "errors" + "fmt" + "strings" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" + "reflect" + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" + "github.com/Azure/sonic-mgmt-common/translib/transformer" + "encoding/json" +) + +var () + +type CommonApp struct { + pathInfo *PathInfo + ygotRoot *ygot.GoStruct + ygotTarget *interface{} + cmnAppTableMap map[string]map[string]db.Value + cmnAppOrdTbllist []string +} + +var cmnAppInfo = appInfo{appType: reflect.TypeOf(CommonApp{}), + ygotRootType: nil, + isNative: false, + tablesToWatch: nil} + +func init() { + + register_model_path := []string{"/sonic-", "*"} // register yang model path(s) to be supported via common app + for _, mdl_pth := range register_model_path { + err := register(mdl_pth, &cmnAppInfo) + + if err != nil { + log.Fatal("Register Common app module with App Interface failed with error=", err, "for path=", mdl_pth) + } + } + +} + +func (app *CommonApp) initialize(data appData) { + log.Info("initialize:path =", data.path) + pathInfo := NewPathInfo(data.path) + *app = CommonApp{pathInfo: pathInfo, ygotRoot: data.ygotRoot, ygotTarget: data.ygotTarget} + +} + +func (app *CommonApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateCreate:path =", app.pathInfo.Path) + + keys, err = app.translateCRUDCommon(d, CREATE) + + return keys, err +} + +func (app *CommonApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateUpdate:path =", app.pathInfo.Path) + + keys, err = app.translateCRUDCommon(d, UPDATE) + + return keys, err +} + +func (app *CommonApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateReplace:path =", app.pathInfo.Path) + + keys, err = app.translateCRUDCommon(d, REPLACE) + + return keys, err +} + +func (app *CommonApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateDelete:path =", app.pathInfo.Path) + keys, err = app.translateCRUDCommon(d, DELETE) + + return keys, err +} + +func (app *CommonApp) translateGet(dbs [db.MaxDB]*db.DB) error { + var err error + log.Info("translateGet:path =", app.pathInfo.Path) + return err +} + +func (app *CommonApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { + err := errors.New("Not supported") + notifInfo := notificationInfo{dbno: db.ConfigDB} + return nil, ¬ifInfo, err +} + +func (app *CommonApp) processCreate(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + log.Info("processCreate:path =", app.pathInfo.Path) + targetType := reflect.TypeOf(*app.ygotTarget) + log.Infof("processCreate: Target object is a <%s> of Type: %s", targetType.Kind().String(), targetType.Elem().Name()) + if err = app.processCommon(d, CREATE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + + return resp, err +} + +func (app *CommonApp) processUpdate(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + log.Info("processUpdate:path =", app.pathInfo.Path) + if err = app.processCommon(d, UPDATE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + + return resp, err +} + +func (app *CommonApp) processReplace(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + log.Info("processReplace:path =", app.pathInfo.Path) + if err = app.processCommon(d, REPLACE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + return resp, err +} + +func (app *CommonApp) processDelete(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + log.Info("processDelete:path =", app.pathInfo.Path) + + if err = app.processCommon(d, DELETE); err != nil { + log.Error(err) + resp = SetResponse{ErrSrc: AppErr} + } + + return resp, err +} + +func (app *CommonApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { + var err error + var payload []byte + var resPayload []byte + log.Info("processGet:path =", app.pathInfo.Path) + + payload, err = transformer.GetAndXlateFromDB(app.pathInfo.Path, app.ygotRoot, dbs) + if err != nil { + log.Error("transformer.transformer.GetAndXlateFromDB failure. error:", err) + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + targetObj, _ := (*app.ygotTarget).(ygot.GoStruct) + if targetObj != nil { + err = ocbinds.Unmarshal(payload, targetObj) + if err != nil { + log.Error("ocbinds.Unmarshal() failed. error:", err) + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + resPayload, err = generateGetResponsePayload(app.pathInfo.Path, (*app.ygotRoot).(*ocbinds.Device), app.ygotTarget) + if err != nil { + log.Error("generateGetResponsePayload() failed") + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + var dat map[string]interface{} + err = json.Unmarshal(resPayload, &dat) + } else { + log.Warning("processGet. targetObj is null. Unable to Unmarshal payload") + resPayload = payload + } + + return GetResponse{Payload: resPayload}, err +} + +func (app *CommonApp) translateCRUDCommon(d *db.DB, opcode int) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + var tblsToWatch []*db.TableSpec + var OrdTblList []string + var moduleNm string + log.Info("translateCRUDCommon:path =", app.pathInfo.Path) + + /* retrieve schema table order for incoming module name request */ + moduleNm, err = transformer.GetModuleNmFromPath(app.pathInfo.Path) + if (err != nil) || (len(moduleNm) == 0) { + log.Error("GetModuleNmFromPath() failed") + return keys, err + } + log.Info("getModuleNmFromPath() returned module name = ", moduleNm) + OrdTblList, err = transformer.GetOrdDBTblList(moduleNm) + if (err != nil) || (len(OrdTblList) == 0) { + log.Error("GetOrdDBTblList() failed") + return keys, err + } + + log.Info("GetOrdDBTblList() returned ordered table list = ", OrdTblList) + app.cmnAppOrdTbllist = OrdTblList + + /* enhance this to handle dependent tables - need CVL to provide list of such tables for a given request */ + for _, tblnm := range OrdTblList { // OrdTblList already has has all tables corresponding to a module + tblsToWatch = append(tblsToWatch, &db.TableSpec{Name: tblnm}) + } + log.Info("Tables to watch", tblsToWatch) + + cmnAppInfo.tablesToWatch = tblsToWatch + + // translate yang to db + result, err := transformer.XlateToDb(app.pathInfo.Path, opcode, d, (*app).ygotRoot, (*app).ygotTarget) + fmt.Println(result) + log.Info("transformer.XlateToDb() returned", result) + + if err != nil { + log.Error(err) + return keys, err + } + if len(result) == 0 { + log.Error("XlatetoDB() returned empty map") + err = errors.New("transformer.XlatetoDB() returned empty map") + return keys, err + } + app.cmnAppTableMap = result + + keys, err = app.generateDbWatchKeys(d, false) + + return keys, err +} + +func (app *CommonApp) processCommon(d *db.DB, opcode int) error { + + var err error + + log.Info("Processing DB operation for ", app.cmnAppTableMap) + switch opcode { + case CREATE: + log.Info("CREATE case") + err = app.cmnAppCRUCommonDbOpn(d, opcode) + case UPDATE: + log.Info("UPDATE case") + err = app.cmnAppCRUCommonDbOpn(d, opcode) + case REPLACE: + log.Info("REPLACE case") + err = app.cmnAppCRUCommonDbOpn(d, opcode) + case DELETE: + log.Info("DELETE case") + err = app.cmnAppDelDbOpn(d, opcode) + } + if err != nil { + log.Info("Returning from processCommon() - fail") + } else { + log.Info("Returning from processCommon() - success") + } + return err +} + +func (app *CommonApp) cmnAppCRUCommonDbOpn(d *db.DB, opcode int) error { + var err error + var cmnAppTs *db.TableSpec + + /* currently ordered by schema table order needs to be discussed */ + for _, tblNm := range app.cmnAppOrdTbllist { + log.Info("In Yang to DB map returned from transformer looking for table = ", tblNm) + if tblVal, ok := app.cmnAppTableMap[tblNm]; ok { + cmnAppTs = &db.TableSpec{Name: tblNm} + log.Info("Found table entry in yang to DB map") + for tblKey, tblRw := range tblVal { + log.Info("Processing Table key and row ", tblKey, tblRw) + existingEntry, _ := d.GetEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}) + switch opcode { + case CREATE: + if existingEntry.IsPopulated() { + log.Info("Entry already exists hence return.") + return tlerr.AlreadyExists("Entry %s already exists", tblKey) + } else { + err = d.CreateEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}, tblRw) + if err != nil { + log.Error("CREATE case - d.CreateEntry() failure") + return err + } + } + case UPDATE: + if existingEntry.IsPopulated() { + log.Info("Entry already exists hence modifying it.") + /* Handle leaf-list merge if any leaf-list exists + A leaf-list field in redis has "@" suffix as per swsssdk convention. + */ + resTblRw := db.Value{Field: map[string]string{}} + resTblRw = checkAndProcessLeafList(existingEntry, tblRw, UPDATE, d, tblNm, tblKey) + err = d.ModEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}, resTblRw) + if err != nil { + log.Error("UPDATE case - d.ModEntry() failure") + return err + } + } else { + // workaround to patch operation from CLI + log.Info("Create(pathc) an entry.") + err = d.CreateEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}, tblRw) + if err != nil { + log.Error("UPDATE case - d.CreateEntry() failure") + return err + } + } + case REPLACE: + if existingEntry.IsPopulated() { + log.Info("Entry already exists hence execute db.SetEntry") + err := d.SetEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}, tblRw) + if err != nil { + log.Error("REPLACE case - d.SetEntry() failure") + return err + } + } else { + log.Info("Entry doesn't exist hence create it.") + err = d.CreateEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}, tblRw) + if err != nil { + log.Error("REPLACE case - d.CreateEntry() failure") + return err + } + } + } + } + } + } + return err +} + +func (app *CommonApp) cmnAppDelDbOpn(d *db.DB, opcode int) error { + var err error + var cmnAppTs, dbTblSpec *db.TableSpec + + /* needs enhancements from CVL to give table dependencies, and grouping of related tables only + if such a case where the sonic yang has unrelated tables */ + for tblidx, tblNm := range app.cmnAppOrdTbllist { + log.Info("In Yang to DB map returned from transformer looking for table = ", tblNm) + if tblVal, ok := app.cmnAppTableMap[tblNm]; ok { + cmnAppTs = &db.TableSpec{Name: tblNm} + log.Info("Found table entry in yang to DB map") + if len(tblVal) == 0 { + log.Info("DELETE case - No table instances/rows found hence delete entire table = ", tblNm) + for idx := len(app.cmnAppOrdTbllist)-1; idx >= tblidx+1; idx-- { + log.Info("Since parent table is to be deleted, first deleting child table = ", app.cmnAppOrdTbllist[idx]) + dbTblSpec = &db.TableSpec{Name: app.cmnAppOrdTbllist[idx]} + err = d.DeleteTable(dbTblSpec) + if err != nil { + log.Warning("DELETE case - d.DeleteTable() failure for Table = ", app.cmnAppOrdTbllist[idx]) + return err + } + } + err = d.DeleteTable(cmnAppTs) + if err != nil { + log.Warning("DELETE case - d.DeleteTable() failure for Table = ", tblNm) + return err + } + log.Info("DELETE case - Deleted entire table = ", tblNm) + log.Info("Done processing all tables.") + break + + } + + for tblKey, tblRw := range tblVal { + if len(tblRw.Field) == 0 { + log.Info("DELETE case - no fields/cols to delete hence delete the entire row.") + log.Info("First, delete child table instances that correspond to parent table instance to be deleted = ", tblKey) + for idx := len(app.cmnAppOrdTbllist)-1; idx >= tblidx+1; idx-- { + dbTblSpec = &db.TableSpec{Name: app.cmnAppOrdTbllist[idx]} + keyPattern := tblKey + "|*" + log.Info("Key pattern to be matched for deletion = ", keyPattern) + err = d.DeleteKeys(dbTblSpec, db.Key{Comp: []string{keyPattern}}) + if err != nil { + log.Warning("DELETE case - d.DeleteTable() failure for Table = ", app.cmnAppOrdTbllist[idx]) + return err + } + log.Info("Deleted keys matching parent table key pattern for child table = ", app.cmnAppOrdTbllist[idx]) + + } + err = d.DeleteEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}) + if err != nil { + log.Warning("DELETE case - d.DeleteEntry() failure") + return err + } + log.Info("Finally deleted the parent table row with key = ", tblKey) + } else { + log.Info("DELETE case - fields/cols to delete hence delete only those fields.") + existingEntry, _ := d.GetEntry(cmnAppTs, db.Key{Comp: []string{tblKey}}) + if !existingEntry.IsPopulated() { + log.Info("Table Entry from which the fields are to be deleted does not exist") + return err + } + /* handle leaf-list merge if any leaf-list exists */ + resTblRw := checkAndProcessLeafList(existingEntry, tblRw, DELETE, d, tblNm, tblKey) + err := d.DeleteEntryFields(cmnAppTs, db.Key{Comp: []string{tblKey}}, resTblRw) + if err != nil { + log.Error("DELETE case - d.DeleteEntryFields() failure") + return err + } + } + + } + } + } /* end of ordered table list for loop */ + return err +} + +func (app *CommonApp) generateDbWatchKeys(d *db.DB, isDeleteOp bool) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + + return keys, err +} + +/*check if any field is leaf-list , if yes perform merge*/ +func checkAndProcessLeafList(existingEntry db.Value, tblRw db.Value, opcode int, d *db.DB, tblNm string, tblKey string) db.Value { + dbTblSpec := &db.TableSpec{Name: tblNm} + mergeTblRw := db.Value{Field: map[string]string{}} + for field, value := range tblRw.Field { + if strings.HasSuffix(field, "@") { + exstLst := existingEntry.GetList(field) + if len(exstLst) != 0 { + valueLst := strings.Split(value, ",") + for _, item := range valueLst { + if !contains(exstLst, item) { + if opcode == UPDATE { + exstLst = append(exstLst, item) + } + } else { + if opcode == DELETE { + exstLst = removeElement(exstLst, item) + } + + } + } + log.Infof("For field %v value after merge %v", field, exstLst) + if opcode == DELETE { + mergeTblRw.SetList(field, exstLst) + delete(tblRw.Field, field) + } + } + tblRw.SetList(field, exstLst) + } + } + /* delete specific item from leaf-list */ + if opcode == DELETE { + if len(mergeTblRw.Field) == 0 { + return tblRw + } + err := d.ModEntry(dbTblSpec, db.Key{Comp: []string{tblKey}}, mergeTblRw) + if err != nil { + log.Warning("DELETE case(merge leaf-list) - d.ModEntry() failure") + } + } + log.Infof("Returning Table Row %v", tblRw) + return tblRw +} + diff --git a/translib/db/db.go b/translib/db/db.go new file mode 100644 index 000000000..0469639fd --- /dev/null +++ b/translib/db/db.go @@ -0,0 +1,1375 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +/* +Package db implements a wrapper over the go-redis/redis. + +There may be an attempt to mimic sonic-py-swsssdk to ease porting of +code written in python using that SDK to Go Language. + +Example: + + * Initialization: + + d, _ := db.NewDB(db.Options { + DBNo : db.ConfigDB, + InitIndicator : "CONFIG_DB_INITIALIZED", + TableNameSeparator: "|", + KeySeparator : "|", + }) + + * Close: + + d.DeleteDB() + + + * No-Transaction SetEntry + + tsa := db.TableSpec { Name: "ACL_TABLE" } + tsr := db.TableSpec { Name: "ACL_RULE" } + + ca := make([]string, 1, 1) + + ca[0] = "MyACL1_ACL_IPV4" + akey := db.Key { Comp: ca} + avalue := db.Value {map[string]string {"ports":"eth0","type":"mirror" }} + + d.SetEntry(&tsa, akey, avalue) + + * GetEntry + + avalue, _ := d.GetEntry(&tsa, akey) + + * GetKeys + + keys, _ := d.GetKeys(&tsa); + + * No-Transaction DeleteEntry + + d.DeleteEntry(&tsa, akey) + + * GetTable + + ta, _ := d.GetTable(&tsa) + + * No-Transaction DeleteTable + + d.DeleteTable(&ts) + + * Transaction + + rkey := db.Key { Comp: []string { "MyACL2_ACL_IPV4", "RULE_1" }} + rvalue := db.Value { Field: map[string]string { + "priority" : "0", + "packet_action" : "eth1", + }, + } + + d.StartTx([]db.WatchKeys { {Ts: &tsr, Key: &rkey} }, + []*db.TableSpec { &tsa, &tsr }) + + d.SetEntry( &tsa, akey, avalue) + d.SetEntry( &tsr, rkey, rvalue) + + e := d.CommitTx() + + * Transaction Abort + + d.StartTx([]db.WatchKeys {}, + []*db.TableSpec { &tsa, &tsr }) + d.DeleteEntry( &tsa, rkey) + d.AbortTx() + + +*/ +package db + +import ( + "fmt" + "strconv" + + // "reflect" + "errors" + "strings" + + "github.com/go-redis/redis" + "github.com/golang/glog" + "github.com/Azure/sonic-mgmt-common/cvl" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" +) + +const ( + DefaultRedisUNIXSocket string = "/var/run/redis/redis.sock" + DefaultRedisLocalTCPEP string = "localhost:6379" + DefaultRedisRemoteTCPEP string = "127.0.0.1:6379" +) + +func init() { +} + +// DBNum type indicates the type of DB (Eg: ConfigDB, ApplDB, ...). +type DBNum int + +const ( + ApplDB DBNum = iota // 0 + AsicDB // 1 + CountersDB // 2 + LogLevelDB // 3 + ConfigDB // 4 + FlexCounterDB // 5 + StateDB // 6 + + // All DBs added above this line, please ---- + MaxDB // 7 The Number of DBs +) + +func(dbNo DBNum) String() string { + return fmt.Sprintf("%d", dbNo) +} + +// Options gives parameters for opening the redis client. +type Options struct { + DBNo DBNum + InitIndicator string + TableNameSeparator string + KeySeparator string + + DisableCVLCheck bool +} + +func (o Options) String() string { + return fmt.Sprintf( + "{ DBNo: %v, InitIndicator: %v, TableNameSeparator: %v, KeySeparator: %v , DisableCVLCheck: %v }", + o.DBNo, o.InitIndicator, o.TableNameSeparator, o.KeySeparator, + o.DisableCVLCheck) +} + +type _txState int + +const ( + txStateNone _txState = iota // Idle (No transaction) + txStateWatch // WATCH issued + txStateSet // At least one Set|Mod|Delete done. + txStateMultiExec // Between MULTI & EXEC +) + +func (s _txState) String() string { + var state string + switch s { + case txStateNone: + state = "txStateNone" + case txStateWatch: + state = "txStateWatch" + case txStateSet: + state = "txStateSet" + case txStateMultiExec: + state = "txStateMultiExec" + default: + state = "Unknown _txState" + } + return state +} + +const ( + InitialTxPipelineSize int = 100 +) + +// TableSpec gives the name of the table, and other per-table customizations. +// (Eg: { Name: ACL_TABLE" }). +type TableSpec struct { + Name string + // https://github.com/project-arlo/sonic-mgmt-framework/issues/29 + // CompCt tells how many components in the key. Only the last component + // can have TableSeparator as part of the key. Otherwise, we cannot + // tell where the key component begins. + CompCt int +} + +// Key gives the key components. +// (Eg: { Comp : [] string { "acl1", "rule1" } } ). +type Key struct { + Comp []string +} + +func (k Key) String() string { + return fmt.Sprintf("{ Comp: %v }", k.Comp) +} + +// Value gives the fields as a map. +// (Eg: { Field: map[string]string { "type" : "l3v6", "ports" : "eth0" } } ). +type Value struct { + Field map[string]string +} + +// Table gives the entire table a a map. +// (Eg: { ts: &TableSpec{ Name: "ACL_TABLE" }, +// entry: map[string]Value { +// "ACL_TABLE|acl1|rule1_1": Value { +// Field: map[string]string { +// "type" : "l3v6", "ports" : "Ethernet0", +// } +// }, +// "ACL_TABLE|acl1|rule1_2": Value { +// Field: map[string]string { +// "type" : "l3v6", "ports" : "eth0", +// } +// }, +// } +// }) + +type Table struct { + ts *TableSpec + entry map[string]Value + db *DB +} + +type _txOp int + +const ( + txOpNone _txOp = iota // No Op + txOpHMSet // key, value gives the field:value to be set in key + txOpHDel // key, value gives the fields to be deleted in key + txOpDel // key +) + +type _txCmd struct { + ts *TableSpec + op _txOp + key *Key + value *Value +} + +// DB is the main type. +type DB struct { + client *redis.Client + Opts *Options + + txState _txState + txCmds []_txCmd + cv *cvl.CVL + cvlEditConfigData [] cvl.CVLEditConfigData + +/* + sKeys []*SKey // Subscribe Key array + sHandler HFunc // Handler Function + sCh <-chan *redis.Message // non-Nil implies SubscribeDB +*/ + sPubSub *redis.PubSub // PubSub. non-Nil implies SubscribeDB + sCIP bool // Close in Progress +} + +func (d DB) String() string { + return fmt.Sprintf("{ client: %v, Opts: %v, txState: %v, tsCmds: %v }", + d.client, d.Opts, d.txState, d.txCmds) +} + +// NewDB is the factory method to create new DB's. +func NewDB(opt Options) (*DB, error) { + + var e error + + if glog.V(3) { + glog.Info("NewDB: Begin: opt: ", opt) + } + + d := DB{client: redis.NewClient(&redis.Options{ + Network: "tcp", + Addr: DefaultRedisLocalTCPEP, + //Addr: DefaultRedisRemoteTCPEP, + Password: "", /* TBD */ + // DB: int(4), /* CONFIG_DB DB No. */ + DB: int(opt.DBNo), + DialTimeout: 0, + // For Transactions, limit the pool + PoolSize: 1, + // Each DB gets it own (single) connection. + }), + Opts: &opt, + txState: txStateNone, + txCmds: make([]_txCmd, 0, InitialTxPipelineSize), + cvlEditConfigData: make([]cvl.CVLEditConfigData, 0, InitialTxPipelineSize), + } + + if d.client == nil { + glog.Error("NewDB: Could not create redis client") + e = tlerr.TranslibDBCannotOpen { } + goto NewDBExit + } + + if opt.DBNo != ConfigDB { + if glog.V(3) { + glog.Info("NewDB: ! ConfigDB. Skip init. check.") + } + goto NewDBSkipInitIndicatorCheck + } + + if len(d.Opts.InitIndicator) == 0 { + + glog.Info("NewDB: Init indication not requested") + + } else if init, _ := d.client.Get(d.Opts.InitIndicator).Int(); init != 1 { + + glog.Error("NewDB: Database not inited") + e = tlerr.TranslibDBNotInit { } + goto NewDBExit + } + +NewDBSkipInitIndicatorCheck: + +NewDBExit: + + if glog.V(3) { + glog.Info("NewDB: End: d: ", d, " e: ", e) + } + + return &d, e +} + +// DeleteDB is the gentle way to close the DB connection. +func (d *DB) DeleteDB() error { + + if glog.V(3) { + glog.Info("DeleteDB: Begin: d: ", d) + } + + if d.txState != txStateNone { + glog.Warning("DeleteDB: not txStateNone, txState: ", d.txState) + } + + return d.client.Close() +} + +func (d *DB) key2redis(ts *TableSpec, key Key) string { + + if glog.V(5) { + glog.Info("key2redis: Begin: ", + ts.Name+ + d.Opts.TableNameSeparator+ + strings.Join(key.Comp, d.Opts.KeySeparator)) + } + return ts.Name + + d.Opts.TableNameSeparator + + strings.Join(key.Comp, d.Opts.KeySeparator) +} + +func (d *DB) redis2key(ts *TableSpec, redisKey string) Key { + + splitTable := strings.SplitN(redisKey, d.Opts.TableNameSeparator, 2) + + if ts.CompCt > 0 { + return Key{strings.SplitN(splitTable[1],d.Opts.KeySeparator, ts.CompCt)} + } else { + return Key{strings.Split(splitTable[1], d.Opts.KeySeparator)} + } + +} + +func (d *DB) ts2redisUpdated(ts *TableSpec) string { + + if glog.V(5) { + glog.Info("ts2redisUpdated: Begin: ", ts.Name) + } + + var updated string + + if strings.Contains(ts.Name, "*") { + updated = string("CONFIG_DB_UPDATED") + } else { + updated = string("CONFIG_DB_UPDATED_") + ts.Name + } + + return updated +} + +// GetEntry retrieves an entry(row) from the table. +func (d *DB) GetEntry(ts *TableSpec, key Key) (Value, error) { + + if glog.V(3) { + glog.Info("GetEntry: Begin: ", "ts: ", ts, " key: ", key) + } + + var value Value + + /* + m := make(map[string]string) + m["f0.0"] = "v0.0" + m["f0.1"] = "v0.1" + m["f0.2"] = "v0.2" + v := Value{Field: m} + */ + + v, e := d.client.HGetAll(d.key2redis(ts, key)).Result() + + if len(v) != 0 { + value = Value{Field: v} + } else { + if glog.V(4) { + glog.Info("GetEntry: HGetAll(): empty map") + } + // e = errors.New("Entry does not exist") + e = tlerr.TranslibRedisClientEntryNotExist { Entry: d.key2redis(ts, key) } + } + + if glog.V(3) { + glog.Info("GetEntry: End: ", "value: ", value, " e: ", e) + } + + return value, e +} + +// GetKeys retrieves all entry/row keys. +func (d *DB) GetKeys(ts *TableSpec) ([]Key, error) { + + if glog.V(3) { + glog.Info("GetKeys: Begin: ", "ts: ", ts) + } + + /* + k := []Key{ + {[]string{"k0.0", "k0.1"}}, + {[]string{"k1.0", "k1.1"}}, + } + */ + redisKeys, e := d.client.Keys(d.key2redis(ts, + Key{Comp: []string{"*"}})).Result() + if glog.V(4) { + glog.Info("GetKeys: redisKeys: ", redisKeys, " e: ", e) + } + + keys := make([]Key, 0, len(redisKeys)) + for i := 0; i < len(redisKeys); i++ { + keys = append(keys, d.redis2key(ts, redisKeys[i])) + } + + if glog.V(3) { + glog.Info("GetKeys: End: ", "keys: ", keys, " e: ", e) + } + + return keys, e +} + +// DeleteKeys deletes all entry/row keys matching a pattern. +func (d *DB) DeleteKeys(ts *TableSpec, key Key) error { + if glog.V(3) { + glog.Info("DeleteKeys: Begin: ", "ts: ", ts, " key: ", key) + } + + // This can be done via a LUA script as well. For now do this. TBD + redisKeys, e := d.client.Keys(d.key2redis(ts, key)).Result() + if glog.V(4) { + glog.Info("DeleteKeys: redisKeys: ", redisKeys, " e: ", e) + } + + for i := 0; i < len(redisKeys); i++ { + if glog.V(4) { + glog.Info("DeleteKeys: Deleting redisKey: ", redisKeys[i]) + } + e = d.DeleteEntry(ts, d.redis2key(ts, redisKeys[i])) + if e != nil { + glog.Warning("DeleteKeys: Deleting: ts: ", ts, " key", + d.redis2key(ts, redisKeys[i]), " : ", e) + } + } + + if glog.V(3) { + glog.Info("DeleteKeys: End: e: ", e) + } + return e +} + + +func (d *DB) doCVL(ts * TableSpec, cvlOps []cvl.CVLOperation, key Key, vals []Value) error { + var e error = nil + + var cvlRetCode cvl.CVLRetCode + var cei cvl.CVLErrorInfo + + if d.Opts.DisableCVLCheck { + glog.Info("doCVL: CVL Disabled. Skipping CVL") + goto doCVLExit + } + + // No Transaction case. No CVL. + if d.txState == txStateNone { + glog.Info("doCVL: No Transactions. Skipping CVL") + goto doCVLExit + } + + if len(cvlOps) != len(vals) { + glog.Error("doCVL: Incorrect arguments len(cvlOps) != len(vals)") + e = errors.New("CVL Incorrect args") + return e + } + for i := 0; i < len(cvlOps); i++ { + + cvlEditConfigData := cvl.CVLEditConfigData { + VType: cvl.VALIDATE_ALL, + VOp: cvlOps[i], + Key: d.key2redis(ts, key), + } + + switch cvlOps[i] { + case cvl.OP_CREATE, cvl.OP_UPDATE: + cvlEditConfigData.Data = vals[i].Field + d.cvlEditConfigData = append(d.cvlEditConfigData, cvlEditConfigData) + + case cvl.OP_DELETE: + if len(vals[i].Field) == 0 { + cvlEditConfigData.Data = map[string]string {} + } else { + cvlEditConfigData.Data = vals[i].Field + } + d.cvlEditConfigData = append(d.cvlEditConfigData, cvlEditConfigData) + + default: + glog.Error("doCVL: Unknown, op: ", cvlOps[i]) + e = errors.New("Unknown Op: " + string(cvlOps[i])) + } + + } + + if e != nil { + goto doCVLExit + } + + if glog.V(3) { + glog.Info("doCVL: calling ValidateEditConfig: ", d.cvlEditConfigData) + } + + cei, cvlRetCode = d.cv.ValidateEditConfig(d.cvlEditConfigData) + + if cvl.CVL_SUCCESS != cvlRetCode { + glog.Error("doCVL: CVL Failure: " , cvlRetCode) + // e = errors.New("CVL Failure: " + string(cvlRetCode)) + e = tlerr.TranslibCVLFailure { Code: int(cvlRetCode), + CVLErrorInfo: cei } + glog.Error("doCVL: " , len(d.cvlEditConfigData), len(cvlOps)) + d.cvlEditConfigData = d.cvlEditConfigData[:len(d.cvlEditConfigData) - len(cvlOps)] + } else { + for i := 0; i < len(cvlOps); i++ { + d.cvlEditConfigData[len(d.cvlEditConfigData)-1-i].VType = cvl.VALIDATE_NONE; + } + } + +doCVLExit: + + if glog.V(3) { + glog.Info("doCVL: End: e: ", e) + } + + return e +} + +func (d *DB) doWrite(ts * TableSpec, op _txOp, key Key, val interface{}) error { + var e error = nil + var value Value + + switch d.txState { + case txStateNone: + if glog.V(2) { + glog.Info("doWrite: No Transaction.") + } + break + case txStateWatch: + if glog.V(2) { + glog.Info("doWrite: Change to txStateSet, txState: ", d.txState) + } + d.txState = txStateSet + break + case txStateSet: + if glog.V(5) { + glog.Info("doWrite: Remain in txStateSet, txState: ", d.txState) + } + case txStateMultiExec: + glog.Error("doWrite: Incorrect State, txState: ", d.txState) + e = errors.New("Cannot issue {Set|Mod|Delete}Entry in txStateMultiExec") + default: + glog.Error("doWrite: Unknown, txState: ", d.txState) + e = errors.New("Unknown State: " + string(d.txState)) + } + + if e != nil { + goto doWriteExit + } + + // No Transaction case. No CVL. + if d.txState == txStateNone { + + switch op { + + case txOpHMSet: + value = Value { Field: make(map[string]string, + len(val.(Value).Field)) } + vintf := make(map[string]interface{}) + for k, v := range val.(Value).Field { + vintf[k] = v + } + e = d.client.HMSet(d.key2redis(ts, key), vintf).Err() + + if e!= nil { + glog.Error("doWrite: HMSet: ", key, " : ", value, " e: ", e) + } + + case txOpHDel: + fields := make([]string, 0, len(val.(Value).Field)) + for k, _ := range val.(Value).Field { + fields = append(fields, k) + } + + e = d.client.HDel(d.key2redis(ts, key), fields...).Err() + if e!= nil { + glog.Error("doWrite: HDel: ", key, " : ", fields, " e: ", e) + } + + case txOpDel: + e = d.client.Del(d.key2redis(ts, key)).Err() + if e!= nil { + glog.Error("doWrite: Del: ", key, " : ", e) + } + + default: + glog.Error("doWrite: Unknown, op: ", op) + e = errors.New("Unknown Op: " + string(op)) + } + + goto doWriteExit + } + + // Transaction case. + + glog.Info("doWrite: op: ", op, " ", key, " : ", value) + + switch op { + case txOpHMSet, txOpHDel: + value = val.(Value) + + case txOpDel: + + default: + glog.Error("doWrite: Unknown, op: ", op) + e = errors.New("Unknown Op: " + string(op)) + } + + if e != nil { + goto doWriteExit + } + + d.txCmds = append(d.txCmds, _txCmd{ + ts: ts, + op: op, + key: &key, + value: &value, + }) + +doWriteExit: + + if glog.V(3) { + glog.Info("doWrite: End: e: ", e) + } + + return e +} + +// setEntry either Creates, or Sets an entry(row) in the table. +func (d *DB) setEntry(ts *TableSpec, key Key, value Value, isCreate bool) error { + + var e error = nil + var valueComplement Value = Value { Field: make(map[string]string,len(value.Field))} + var valueCurrent Value + + if glog.V(3) { + glog.Info("setEntry: Begin: ", "ts: ", ts, " key: ", key, + " value: ", value, " isCreate: ", isCreate) + } + + if len(value.Field) == 0 { + glog.Info("setEntry: Mapping to DeleteEntry()") + e = d.DeleteEntry(ts, key) + goto setEntryExit + } + + if isCreate == false { + // Prepare the HDel list + // Note: This is for compatibililty with PySWSSDK semantics. + // The CVL library will likely fail the SetEntry when + // the item exists. + valueCurrent, e = d.GetEntry(ts, key) + if e == nil { + for k, _ := range valueCurrent.Field { + _, present := value.Field[k] + if ! present { + valueComplement.Field[k] = string("") + } + } + } + } + + if isCreate == false && e == nil { + if glog.V(3) { + glog.Info("setEntry: DoCVL for UPDATE") + } + if len(valueComplement.Field) == 0 { + e = d.doCVL(ts, []cvl.CVLOperation {cvl.OP_UPDATE}, + key, []Value { value} ) + } else { + e = d.doCVL(ts, []cvl.CVLOperation {cvl.OP_UPDATE, cvl.OP_DELETE}, + key, []Value { value, valueComplement} ) + } + } else { + if glog.V(3) { + glog.Info("setEntry: DoCVL for CREATE") + } + e = d.doCVL(ts, []cvl.CVLOperation {cvl.OP_CREATE}, key, []Value { value }) + } + + if e != nil { + goto setEntryExit + } + + e = d.doWrite(ts, txOpHMSet, key, value) + + if (e == nil) && (len(valueComplement.Field) != 0) { + if glog.V(3) { + glog.Info("setEntry: DoCVL for HDEL (post-POC)") + } + e = d.doWrite(ts, txOpHDel, key, valueComplement) + } + +setEntryExit: + return e +} + +// CreateEntry creates an entry(row) in the table. +func (d * DB) CreateEntry(ts * TableSpec, key Key, value Value) error { + + return d.setEntry(ts, key, value, true) +} + +// SetEntry sets an entry(row) in the table. +func (d *DB) SetEntry(ts *TableSpec, key Key, value Value) error { + return d.setEntry(ts, key, value, false) +} + +// DeleteEntry deletes an entry(row) in the table. +func (d *DB) DeleteEntry(ts *TableSpec, key Key) error { + + var e error = nil + if glog.V(3) { + glog.Info("DeleteEntry: Begin: ", "ts: ", ts, " key: ", key) + } + + if glog.V(3) { + glog.Info("DeleteEntry: DoCVL for DELETE") + } + e = d.doCVL(ts, []cvl.CVLOperation {cvl.OP_DELETE}, key, []Value {Value{}}) + + if e == nil { + e = d.doWrite(ts, txOpDel, key, nil) + } + + return e; +} + +// ModEntry modifies an entry(row) in the table. +func (d *DB) ModEntry(ts *TableSpec, key Key, value Value) error { + + var e error = nil + + if glog.V(3) { + glog.Info("ModEntry: Begin: ", "ts: ", ts, " key: ", key, + " value: ", value) + } + + if len(value.Field) == 0 { + glog.Info("ModEntry: Mapping to DeleteEntry()") + e = d.DeleteEntry(ts, key) + goto ModEntryExit + } + + if glog.V(3) { + glog.Info("ModEntry: DoCVL for UPDATE") + } + e = d.doCVL(ts, []cvl.CVLOperation {cvl.OP_UPDATE}, key, []Value {value}) + + if e == nil { + e = d.doWrite(ts, txOpHMSet, key, value) + } + +ModEntryExit: + + return e +} + +// DeleteEntryFields deletes some fields/columns in an entry(row) in the table. +func (d *DB) DeleteEntryFields(ts *TableSpec, key Key, value Value) error { + + if glog.V(3) { + glog.Info("DeleteEntryFields: Begin: ", "ts: ", ts, " key: ", key, + " value: ", value) + } + + if glog.V(3) { + glog.Info("DeleteEntryFields: DoCVL for HDEL (post-POC)") + } + + if glog.V(3) { + glog.Info("DeleteEntryFields: DoCVL for HDEL") + } + + e := d.doCVL(ts, []cvl.CVLOperation {cvl.OP_DELETE}, key, []Value{value}) + + if e == nil { + d.doWrite(ts, txOpHDel, key, value) + } + + return e +} + + +// GetTable gets the entire table. +func (d *DB) GetTable(ts *TableSpec) (Table, error) { + if glog.V(3) { + glog.Info("GetTable: Begin: ts: ", ts) + } + + /* + table := Table{ + ts: ts, + entry: map[string]Value{ + "table1|k0.0|k0.1": Value{ + map[string]string{ + "f0.0": "v0.0", + "f0.1": "v0.1", + "f0.2": "v0.2", + }, + }, + "table1|k1.0|k1.1": Value{ + map[string]string{ + "f1.0": "v1.0", + "f1.1": "v1.1", + "f1.2": "v1.2", + }, + }, + }, + db: d, + } + */ + + // Create Table + table := Table{ + ts: ts, + entry: make(map[string]Value), + db: d, + } + + // This can be done via a LUA script as well. For now do this. TBD + // Read Keys + keys, e := d.GetKeys(ts) + if e != nil { + glog.Error("GetTable: GetKeys: " + e.Error()) + goto GetTableExit + } + + // For each key in Keys + // Add Value into table.entry[key)] + for i := 0; i < len(keys); i++ { + value, e := d.GetEntry(ts, keys[i]) + if e != nil { + glog.Warning("GetTable: GetKeys: " + e.Error()) + continue + } + table.entry[d.key2redis(ts, keys[i])] = value + } + +GetTableExit: + + if glog.V(3) { + glog.Info("GetTable: End: table: ", table) + } + return table, e +} + +// DeleteTable deletes the entire table. +func (d *DB) DeleteTable(ts *TableSpec) error { + if glog.V(3) { + glog.Info("DeleteTable: Begin: ts: ", ts) + } + + // This can be done via a LUA script as well. For now do this. TBD + // Read Keys + keys, e := d.GetKeys(ts) + if e != nil { + glog.Error("DeleteTable: GetKeys: " + e.Error()) + goto DeleteTableExit + } + + // For each key in Keys + // Delete the entry + for i := 0; i < len(keys); i++ { + e := d.DeleteEntry(ts, keys[i]) + if e != nil { + glog.Warning("DeleteTable: DeleteEntry: " + e.Error()) + continue + } + } +DeleteTableExit: + if glog.V(3) { + glog.Info("DeleteTable: End: ") + } + return e +} + +// GetKeys method retrieves all entry/row keys from a previously read table. +func (t *Table) GetKeys() ([]Key, error) { + if glog.V(3) { + glog.Info("Table.GetKeys: Begin: t: ", t) + } + keys := make([]Key, 0, len(t.entry)) + for k, _ := range t.entry { + keys = append(keys, t.db.redis2key(t.ts, k)) + } + + if glog.V(3) { + glog.Info("Table.GetKeys: End: keys: ", keys) + } + return keys, nil +} + +// GetEntry method retrieves an entry/row from a previously read table. +func (t *Table) GetEntry(key Key) (Value, error) { + /* + return Value{map[string]string{ + "f0.0": "v0.0", + "f0.1": "v0.1", + "f0.2": "v0.2", + }, + }, nil + */ + if glog.V(3) { + glog.Info("Table.GetEntry: Begin: t: ", t, " key: ", key) + } + v := t.entry[t.db.key2redis(t.ts, key)] + if glog.V(3) { + glog.Info("Table.GetEntry: End: entry: ", v) + } + return v, nil +} + +//===== Functions for db.Key ===== + +// Len returns number of components in the Key +func (k *Key) Len() int { + return len(k.Comp) +} + +// Get returns the key component at given index +func (k *Key) Get(index int) string { + return k.Comp[index] +} + +//===== Functions for db.Value ===== + +func (v *Value) IsPopulated() bool { + return len(v.Field) > 0 +} + +// Has function checks if a field exists. +func (v *Value) Has(name string) bool { + _, flag := v.Field[name] + return flag +} + +// Get returns the value of a field. Returns empty string if the field +// does not exists. Use Has() function to check existance of field. +func (v *Value) Get(name string) string { + return v.Field[name] +} + +// Set function sets a string value for a field. +func (v *Value) Set(name, value string) { + v.Field[name] = value +} + +// GetInt returns value of a field as int. Returns 0 if the field does +// not exists. Returns an error if the field value is not a number. +func (v *Value) GetInt(name string) (int, error) { + data, ok := v.Field[name] + if ok { + return strconv.Atoi(data) + } + return 0, nil +} + +// SetInt sets an integer value for a field. +func (v *Value) SetInt(name string, value int) { + v.Set(name, strconv.Itoa(value)) +} + +// GetList returns the value of a an array field. A "@" suffix is +// automatically appended to the field name if not present (as per +// swsssdk convention). Field value is split by comma and resulting +// slice is returned. Empty slice is returned if field not exists. +func (v *Value) GetList(name string) []string { + var data string + if strings.HasSuffix(name, "@") { + data = v.Get(name) + } else { + data = v.Get(name + "@") + } + + if len(data) == 0 { + return []string{} + } + + return strings.Split(data, ",") +} + +// SetList function sets an list value to a field. Field name and +// value are formatted as per swsssdk conventions: +// - A "@" suffix is appended to key name +// - Field value is the comma separated string of list items +func (v *Value) SetList(name string, items []string) { + if !strings.HasSuffix(name, "@") { + name += "@" + } + + if len(items) != 0 { + data := strings.Join(items, ",") + v.Set(name, data) + } else { + v.Remove(name) + } +} + +// Remove function removes a field from this Value. +func (v *Value) Remove(name string) { + delete(v.Field, name) +} + +////////////////////////////////////////////////////////////////////////// +// The Transaction API for translib infra +////////////////////////////////////////////////////////////////////////// + +// WatchKeys is array of (TableSpec, Key) tuples to be watched in a Transaction. +type WatchKeys struct { + Ts *TableSpec + Key *Key +} + +func (w WatchKeys) String() string { + return fmt.Sprintf("{ Ts: %v, Key: %v }", w.Ts, w.Key) +} + +// Convenience function to make TableSpecs from strings. +// This only works on Tables having key components without TableSeparator +// as part of the key. +func Tables2TableSpecs(tables []string) []* TableSpec { + var tss []*TableSpec + + tss = make([]*TableSpec, 0, len(tables)) + + for i := 0; i < len(tables); i++ { + tss = append(tss, &(TableSpec{ Name: tables[i]})) + } + + return tss +} + +// StartTx method is used by infra to start a check-and-set Transaction. +func (d *DB) StartTx(w []WatchKeys, tss []*TableSpec) error { + + if glog.V(3) { + glog.Info("StartTx: Begin: w: ", w, " tss: ", tss) + } + + var e error = nil + var args []interface{} + var ret cvl.CVLRetCode + + //Start CVL session + if d.cv, ret = cvl.ValidationSessOpen(); ret != cvl.CVL_SUCCESS { + e = errors.New("StartTx: Unable to create CVL session") + goto StartTxExit + } + + // Validate State + if d.txState != txStateNone { + glog.Error("StartTx: Incorrect State, txState: ", d.txState) + e = errors.New("Transaction already in progress") + goto StartTxExit + } + + // For each watchkey + // If a pattern, Get the keys, appending results to Cmd args. + // Else append keys to the Cmd args + // Note: (LUA scripts do not support WATCH) + + args = make([]interface{}, 0, len(w) + len(tss) + 1) + args = append(args, "WATCH") + for i := 0; i < len(w); i++ { + + redisKey := d.key2redis(w[i].Ts, *(w[i].Key)) + + if !strings.Contains(redisKey, "*") { + args = append(args, redisKey) + continue + } + + redisKeys, e := d.client.Keys(redisKey).Result() + if e != nil { + glog.Warning("StartTx: Keys: " + e.Error()) + continue + } + for j := 0; j < len(redisKeys); j++ { + args = append(args, d.redis2key(w[i].Ts, redisKeys[j])) + } + } + + // for each TS, append to args the CONFIG_DB_UPDATED_ key + + for i := 0; i < len(tss); i++ { + args = append( args, d.ts2redisUpdated(tss[i])) + } + + if len(args) == 1 { + glog.Warning("StartTx: Empty WatchKeys. Skipping WATCH") + goto StartTxSkipWatch + } + + // Issue the WATCH + _, e = d.client.Do(args...).Result() + + if e != nil { + glog.Warning("StartTx: Do: WATCH ", args, " e: ", e.Error()) + } + +StartTxSkipWatch: + + // Switch State + d.txState = txStateWatch + +StartTxExit: + + if glog.V(3) { + glog.Info("StartTx: End: e: ", e) + } + return e +} + +// CommitTx method is used by infra to commit a check-and-set Transaction. +func (d *DB) CommitTx() error { + if glog.V(3) { + glog.Info("CommitTx: Begin:") + } + + var e error = nil + var tsmap map[TableSpec]bool = + make(map[TableSpec]bool, len(d.txCmds)) // UpperBound + + // Validate State + switch d.txState { + case txStateNone: + glog.Error("CommitTx: No WATCH done, txState: ", d.txState) + e = errors.New("StartTx() not done. No Transaction active.") + case txStateWatch: + if glog.V(1) { + glog.Info("CommitTx: No SET|DEL done, txState: ", d.txState) + } + case txStateSet: + break + case txStateMultiExec: + glog.Error("CommitTx: Incorrect State, txState: ", d.txState) + e = errors.New("Cannot issue MULTI in txStateMultiExec") + default: + glog.Error("CommitTx: Unknown, txState: ", d.txState) + e = errors.New("Unknown State: " + string(d.txState)) + } + + if e != nil { + goto CommitTxExit + } + + // Issue MULTI + _, e = d.client.Do("MULTI").Result() + + if e != nil { + glog.Warning("CommitTx: Do: MULTI e: ", e.Error()) + } + + // For each cmd in txCmds + // Invoke it + for i := 0; i < len(d.txCmds); i++ { + + var args []interface{} + + redisKey := d.key2redis(d.txCmds[i].ts, *(d.txCmds[i].key)) + + // Add TS to the map of watchTables + tsmap[*(d.txCmds[i].ts)] = true; + + switch d.txCmds[i].op { + + case txOpHMSet: + + args = make([]interface{}, 0, len(d.txCmds[i].value.Field)*2+2) + args = append(args, "HMSET", redisKey) + + for k, v := range d.txCmds[i].value.Field { + args = append(args, k, v) + } + + if glog.V(4) { + glog.Info("CommitTx: Do: ", args) + } + + _, e = d.client.Do(args...).Result() + + case txOpHDel: + + args = make([]interface{}, 0, len(d.txCmds[i].value.Field)+2) + args = append(args, "HDEL", redisKey) + + for k, _ := range d.txCmds[i].value.Field { + args = append(args, k) + } + + if glog.V(4) { + glog.Info("CommitTx: Do: ", args) + } + + _, e = d.client.Do(args...).Result() + + case txOpDel: + + args = make([]interface{}, 0, 2) + args = append(args, "DEL", redisKey) + + if glog.V(4) { + glog.Info("CommitTx: Do: ", args) + } + + _, e = d.client.Do(args...).Result() + + default: + glog.Error("CommitTx: Unknown, op: ", d.txCmds[i].op) + e = errors.New("Unknown Op: " + string(d.txCmds[i].op)) + } + + if e != nil { + glog.Warning("CommitTx: Do: ", args, " e: ", e.Error()) + } + } + + // Flag the Tables as updated. + for ts, _ := range tsmap { + _, e = d.client.Do("SET", d.ts2redisUpdated(&ts), "1").Result() + if e != nil { + glog.Warning("CommitTx: Do: SET ", + d.ts2redisUpdated(&ts), " 1: e: ", + e.Error()) + } + } + _, e = d.client.Do("SET", d.ts2redisUpdated(& TableSpec{Name: "*"}), + "1").Result() + if e != nil { + glog.Warning("CommitTx: Do: SET ", + "CONFIG_DB_UPDATED", " 1: e: ", e.Error()) + } + + // Issue EXEC + _, e = d.client.Do("EXEC").Result() + + if e != nil { + glog.Warning("CommitTx: Do: EXEC e: ", e.Error()) + e = tlerr.TranslibTransactionFail { } + } + + // Switch State, Clear Command list + d.txState = txStateNone + d.txCmds = d.txCmds[:0] + d.cvlEditConfigData = d.cvlEditConfigData[:0] + + //Close CVL session + if ret := cvl.ValidationSessClose(d.cv); ret != cvl.CVL_SUCCESS { + glog.Error("CommitTx: End: Error in closing CVL session") + } + d.cv = nil + +CommitTxExit: + if glog.V(3) { + glog.Info("CommitTx: End: e: ", e) + } + return e +} + +// AbortTx method is used by infra to abort a check-and-set Transaction. +func (d *DB) AbortTx() error { + if glog.V(3) { + glog.Info("AbortTx: Begin:") + } + + var e error = nil + + // Validate State + switch d.txState { + case txStateNone: + glog.Error("AbortTx: No WATCH done, txState: ", d.txState) + e = errors.New("StartTx() not done. No Transaction active.") + case txStateWatch: + if glog.V(1) { + glog.Info("AbortTx: No SET|DEL done, txState: ", d.txState) + } + case txStateSet: + break + case txStateMultiExec: + glog.Error("AbortTx: Incorrect State, txState: ", d.txState) + e = errors.New("Cannot issue UNWATCH in txStateMultiExec") + default: + glog.Error("AbortTx: Unknown, txState: ", d.txState) + e = errors.New("Unknown State: " + string(d.txState)) + } + + if e != nil { + goto AbortTxExit + } + + // Issue UNWATCH + _, e = d.client.Do("UNWATCH").Result() + + if e != nil { + glog.Warning("AbortTx: Do: UNWATCH e: ", e.Error()) + } + + // Switch State, Clear Command list + d.txState = txStateNone + d.txCmds = d.txCmds[:0] + d.cvlEditConfigData = d.cvlEditConfigData[:0] + + //Close CVL session + if ret := cvl.ValidationSessClose(d.cv); ret != cvl.CVL_SUCCESS { + glog.Error("AbortTx: End: Error in closing CVL session") + } + d.cv = nil + +AbortTxExit: + if glog.V(3) { + glog.Info("AbortTx: End: e: ", e) + } + return e +} diff --git a/translib/db/db_test.go b/translib/db/db_test.go new file mode 100644 index 000000000..59402d33b --- /dev/null +++ b/translib/db/db_test.go @@ -0,0 +1,610 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package db + + +import ( + // "fmt" + // "errors" + // "flag" + // "github.com/golang/glog" + "time" + // "github.com/Azure/sonic-mgmt-common/translib/tlerr" + // "os/exec" + "os" + "testing" + "strconv" + "reflect" +) + +func TestMain(m * testing.M) { + + exitCode := 0 + +/* Apparently, on an actual switch the swss container will have + * a redis-server running, which will be in a different container than + * mgmt, thus this pkill stuff to find out it is running will not work. + * + + redisServerAttemptedStart := false + +TestMainRedo: + o, e := exec.Command("/usr/bin/pkill", "-HUP", "redis-server").Output() + + if e == nil { + + } else if redisServerAttemptedStart { + + exitCode = 1 + + } else { + + fmt.Printf("TestMain: No redis server: pkill: %v\n", o) + fmt.Println("TestMain: Starting redis-server") + e = exec.Command("/tools/bin/redis-server").Start() + time.Sleep(3 * time.Second) + redisServerAttemptedStart = true + goto TestMainRedo + } +*/ + + if exitCode == 0 { + exitCode = m.Run() + } + + + os.Exit(exitCode) + +} + +/* + +1. Create, and close a DB connection. (NewDB(), DeleteDB()) + +*/ + +func TestNewDB(t * testing.T) { + + d,e := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + if d == nil { + t.Errorf("NewDB() fails e = %v", e) + } else if e = d.DeleteDB() ; e != nil { + t.Errorf("DeleteDB() fails e = %v", e) + } +} + + +/* + +2. Get an entry (GetEntry()) +3. Set an entry without Transaction (SetEntry()) +4. Delete an entry without Transaction (DeleteEntry()) + +20. NT: GetEntry() EntryNotExist. + +*/ + +func TestNoTransaction(t * testing.T) { + + var pid int = os.Getpid() + + d,e := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + if d == nil { + t.Errorf("NewDB() fails e = %v", e) + return + } + + ts := TableSpec { Name: "TEST_" + strconv.FormatInt(int64(pid), 10) } + + ca := make([]string, 1, 1) + ca[0] = "MyACL1_ACL_IPVNOTEXIST" + akey := Key { Comp: ca} + avalue := Value { map[string]string {"ports@":"Ethernet0","type":"MIRROR" }} + e = d.SetEntry(&ts, akey, avalue) + + if e != nil { + t.Errorf("SetEntry() fails e = %v", e) + return + } + + v, e := d.GetEntry(&ts, akey) + + if (e != nil) || (!reflect.DeepEqual(v,avalue)) { + t.Errorf("GetEntry() fails e = %v", e) + return + } + + e = d.DeleteEntry(&ts, akey) + + if e != nil { + t.Errorf("DeleteEntry() fails e = %v", e) + return + } + + v, e = d.GetEntry(&ts, akey) + + if e == nil { + t.Errorf("GetEntry() after DeleteEntry() fails e = %v", e) + return + } + + if e = d.DeleteDB() ; e != nil { + t.Errorf("DeleteDB() fails e = %v", e) + } +} + + +/* + +5. Get a Table (GetTable()) + +9. Get multiple keys (GetKeys()) +10. Delete multiple keys (DeleteKeys()) +11. Delete Table (DeleteTable()) + +*/ + +func TestTable(t * testing.T) { + + var pid int = os.Getpid() + + d,e := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + if d == nil { + t.Errorf("NewDB() fails e = %v", e) + return + } + + ts := TableSpec { Name: "TEST_" + strconv.FormatInt(int64(pid), 10) } + + ca := make([]string, 1, 1) + ca[0] = "MyACL1_ACL_IPVNOTEXIST" + akey := Key { Comp: ca} + avalue := Value { map[string]string {"ports@":"Ethernet0","type":"MIRROR" }} + ca2 := make([]string, 1, 1) + ca2[0] = "MyACL2_ACL_IPVNOTEXIST" + akey2 := Key { Comp: ca2} + + // Add the Entries for Get|DeleteKeys + + e = d.SetEntry(&ts, akey, avalue) + + if e != nil { + t.Errorf("SetEntry() fails e = %v", e) + return + } + + e = d.SetEntry(&ts, akey2, avalue) + + if e != nil { + t.Errorf("SetEntry() fails e = %v", e) + return + } + + keys, e := d.GetKeys(&ts) + + if (e != nil) || (len(keys) != 2) { + t.Errorf("GetKeys() fails e = %v", e) + return + } + + e = d.DeleteKeys(&ts, Key {Comp: []string {"MyACL*_ACL_IPVNOTEXIST"}}) + + if e != nil { + t.Errorf("DeleteKeys() fails e = %v", e) + return + } + + v, e := d.GetEntry(&ts, akey) + + if e == nil { + t.Errorf("GetEntry() after DeleteKeys() fails e = %v", e) + return + } + + + + // Add the Entries again for Table + + e = d.SetEntry(&ts, akey, avalue) + + if e != nil { + t.Errorf("SetEntry() fails e = %v", e) + return + } + + e = d.SetEntry(&ts, akey2, avalue) + + if e != nil { + t.Errorf("SetEntry() fails e = %v", e) + return + } + + tab, e := d.GetTable(&ts) + + if e != nil { + t.Errorf("GetTable() fails e = %v", e) + return + } + + v, e = tab.GetEntry(akey) + + if (e != nil) || (!reflect.DeepEqual(v,avalue)) { + t.Errorf("Table.GetEntry() fails e = %v", e) + return + } + + e = d.DeleteTable(&ts) + + if e != nil { + t.Errorf("DeleteTable() fails e = %v", e) + return + } + + v, e = d.GetEntry(&ts, akey) + + if e == nil { + t.Errorf("GetEntry() after DeleteTable() fails e = %v", e) + return + } + + if e = d.DeleteDB() ; e != nil { + t.Errorf("DeleteDB() fails e = %v", e) + } +} + + +/* Tests for + +6. Set an entry with Transaction (StartTx(), SetEntry(), CommitTx()) +7. Delete an entry with Transaction (StartTx(), DeleteEntry(), CommitTx()) +8. Abort Transaction. (StartTx(), DeleteEntry(), AbortTx()) + +12. Set an entry with Transaction using WatchKeys Check-And-Set(CAS) +13. Set an entry with Transaction using Table CAS +14. Set an entry with Transaction using WatchKeys, and Table CAS + +15. Set an entry with Transaction with empty WatchKeys, and Table CAS +16. Negative Test(NT): Fail a Transaction using WatchKeys CAS +17. NT: Fail a Transaction using Table CAS +18. NT: Abort an Transaction with empty WatchKeys/Table CAS + +Cannot Automate 19 for now +19. NT: Check V logs, Error logs + + */ + +func TestTransaction(t * testing.T) { + for transRun := TransRunBasic ; transRun < TransRunEnd ; transRun++ { + testTransaction(t, transRun) + } +} + +type TransRun int + +const ( + TransRunBasic TransRun = iota // 0 + TransRunWatchKeys // 1 + TransRunTable // 2 + TransRunWatchKeysAndTable // 3 + TransRunEmptyWatchKeysAndTable // 4 + TransRunFailWatchKeys // 5 + TransRunFailTable // 6 + + // Nothing after this. + TransRunEnd +) + +func testTransaction(t * testing.T, transRun TransRun) { + + var pid int = os.Getpid() + + d,e := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + if d == nil { + t.Errorf("NewDB() fails e = %v, transRun = %v", e, transRun) + return + } + + ts := TableSpec { Name: "TEST_" + strconv.FormatInt(int64(pid), 10) } + + ca := make([]string, 1, 1) + ca[0] = "MyACL1_ACL_IPVNOTEXIST" + akey := Key { Comp: ca} + avalue := Value { map[string]string {"ports@":"Ethernet0","type":"MIRROR" }} + + var watchKeys []WatchKeys + var table []*TableSpec + + switch transRun { + case TransRunBasic, TransRunWatchKeysAndTable: + watchKeys = []WatchKeys{{Ts: &ts, Key: &akey}} + table = []*TableSpec { &ts } + case TransRunWatchKeys, TransRunFailWatchKeys: + watchKeys = []WatchKeys{{Ts: &ts, Key: &akey}} + table = []*TableSpec { } + case TransRunTable, TransRunFailTable: + watchKeys = []WatchKeys{} + table = []*TableSpec { &ts } + } + + e = d.StartTx(watchKeys, table) + + if e != nil { + t.Errorf("StartTx() fails e = %v", e) + return + } + + e = d.SetEntry(&ts, akey, avalue) + + if e != nil { + t.Errorf("SetEntry() fails e = %v", e) + return + } + + e = d.CommitTx() + + if e != nil { + t.Errorf("CommitTx() fails e = %v", e) + return + } + + v, e := d.GetEntry(&ts, akey) + + if (e != nil) || (!reflect.DeepEqual(v,avalue)) { + t.Errorf("GetEntry() after Tx fails e = %v", e) + return + } + + e = d.StartTx(watchKeys, table) + + if e != nil { + t.Errorf("StartTx() fails e = %v", e) + return + } + + e = d.DeleteEntry(&ts, akey) + + if e != nil { + t.Errorf("DeleteEntry() fails e = %v", e) + return + } + + e = d.AbortTx() + + if e != nil { + t.Errorf("AbortTx() fails e = %v", e) + return + } + + v, e = d.GetEntry(&ts, akey) + + if (e != nil) || (!reflect.DeepEqual(v,avalue)) { + t.Errorf("GetEntry() after Abort Tx fails e = %v", e) + return + } + + e = d.StartTx(watchKeys, table) + + if e != nil { + t.Errorf("StartTx() fails e = %v", e) + return + } + + e = d.DeleteEntry(&ts, akey) + + if e != nil { + t.Errorf("DeleteEntry() fails e = %v", e) + return + } + + switch transRun { + case TransRunFailWatchKeys, TransRunFailTable: + d2,_ := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + d2.StartTx(watchKeys, table); + d2.DeleteEntry(&ts, akey) + d2.CommitTx(); + d2.DeleteDB(); + default: + } + + e = d.CommitTx() + + switch transRun { + case TransRunFailWatchKeys, TransRunFailTable: + if e == nil { + t.Errorf("NT CommitTx() tr: %v fails e = %v", + transRun, e) + return + } + default: + if e != nil { + t.Errorf("CommitTx() fails e = %v", e) + return + } + } + + v, e = d.GetEntry(&ts, akey) + + if e == nil { + t.Errorf("GetEntry() after Tx DeleteEntry() fails e = %v", e) + return + } + + d.DeleteMapAll(&ts) + + if e = d.DeleteDB() ; e != nil { + t.Errorf("DeleteDB() fails e = %v", e) + } +} + + +func TestMap(t * testing.T) { + + var pid int = os.Getpid() + + d,e := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + if d == nil { + t.Errorf("NewDB() fails e = %v", e) + return + } + + ts := TableSpec { Name: "TESTMAP_" + strconv.FormatInt(int64(pid), 10) } + + d.SetMap(&ts, "k1", "v1"); + d.SetMap(&ts, "k2", "v2"); + + if v, e := d.GetMap(&ts, "k1"); v != "v1" { + t.Errorf("GetMap() fails e = %v", e) + return + } + + if v, e := d.GetMapAll(&ts) ; + (e != nil) || + (!reflect.DeepEqual(v, + Value{ Field: map[string]string { + "k1" : "v1", "k2" : "v2" }})) { + t.Errorf("GetMapAll() fails e = %v", e) + return + } + + d.DeleteMapAll(&ts) + + if e = d.DeleteDB() ; e != nil { + t.Errorf("DeleteDB() fails e = %v", e) + } +} + +func TestSubscribe(t * testing.T) { + + var pid int = os.Getpid() + + var hSetCalled, hDelCalled, delCalled bool + + d,e := NewDB(Options { + DBNo : ConfigDB, + InitIndicator : "", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }) + + if (d == nil) || (e != nil) { + t.Errorf("NewDB() fails e = %v", e) + return + } + + ts := TableSpec { Name: "TEST_" + strconv.FormatInt(int64(pid), 10) } + + ca := make([]string, 1, 1) + ca[0] = "MyACL1_ACL_IPVNOTEXIST" + akey := Key { Comp: ca} + avalue := Value { map[string]string {"ports@":"Ethernet0","type":"MIRROR" }} + + var skeys [] *SKey = make([]*SKey, 1) + skeys[0] = & (SKey { Ts: &ts, Key: &akey, + SEMap: map[SEvent]bool { + SEventHSet: true, + SEventHDel: true, + SEventDel: true, + }}) + + s,e := SubscribeDB(Options { + DBNo : ConfigDB, + InitIndicator : "CONFIG_DB_INITIALIZED", + TableNameSeparator: "|", + KeySeparator : "|", + DisableCVLCheck : true, + }, skeys, func (s *DB, + skey *SKey, key *Key, + event SEvent) error { + switch event { + case SEventHSet: hSetCalled = true + case SEventHDel: hDelCalled = true + case SEventDel: delCalled = true + default: + } + return nil }) + + if (s == nil) || (e != nil) { + t.Errorf("Subscribe() returns error e: %v", e) + return + } + + d.SetEntry(&ts, akey, avalue) + d.DeleteEntryFields(&ts, akey, avalue) + + time.Sleep(5 * time.Second) + + if !hSetCalled || !hDelCalled || !delCalled { + t.Errorf("Subscribe() callbacks missed: %v %v %v", hSetCalled, + hDelCalled, delCalled) + return + } + + s.UnsubscribeDB() + + time.Sleep(2 * time.Second) + + if e = d.DeleteDB() ; e != nil { + t.Errorf("DeleteDB() fails e = %v", e) + } +} + diff --git a/translib/db/map.go b/translib/db/map.go new file mode 100644 index 000000000..781800207 --- /dev/null +++ b/translib/db/map.go @@ -0,0 +1,123 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +/* +Package db implements a wrapper over the go-redis/redis. +*/ +package db + +import ( + // "fmt" + // "strconv" + + // "reflect" + // "errors" + // "strings" + + // "github.com/go-redis/redis" + "github.com/golang/glog" + // "github.com/Azure/sonic-mgmt-common/cvl" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" +) + +func init() { +} + + + + +func (d *DB) GetMap(ts *TableSpec, mapKey string) (string, error) { + + if glog.V(3) { + glog.Info("GetMap: Begin: ", "ts: ", ts, " mapKey: ", mapKey) + } + + v, e := d.client.HGet(ts.Name, mapKey).Result() + + if glog.V(3) { + glog.Info("GetMap: End: ", "v: ", v, " e: ", e) + } + + return v, e +} + +func (d *DB) GetMapAll(ts *TableSpec) (Value, error) { + + if glog.V(3) { + glog.Info("GetMapAll: Begin: ", "ts: ", ts) + } + + var value Value + + v, e := d.client.HGetAll(ts.Name).Result() + + if len(v) != 0 { + value = Value{Field: v} + } else { + if glog.V(1) { + glog.Info("GetMapAll: HGetAll(): empty map") + } + e = tlerr.TranslibRedisClientEntryNotExist { Entry: ts.Name } + } + + if glog.V(3) { + glog.Info("GetMapAll: End: ", "value: ", value, " e: ", e) + } + + return value, e +} + +// For Testing only. Do Not Use!!! ============================== +// There is no transaction support on these. +func (d *DB) SetMap(ts *TableSpec, mapKey string, mapValue string) error { + + if glog.V(3) { + glog.Info("SetMap: Begin: ", "ts: ", ts, " ", mapKey, + ":", mapValue) + } + + b, e := d.client.HSet(ts.Name, mapKey, mapValue).Result() + + if glog.V(3) { + glog.Info("GetMap: End: ", "b: ", b, " e: ", e) + } + + return e +} +// For Testing only. Do Not Use!!! ============================== + +// For Testing only. Do Not Use!!! +// There is no transaction support on these. +func (d *DB) DeleteMapAll(ts *TableSpec) error { + + if glog.V(3) { + glog.Info("DeleteMapAll: Begin: ", "ts: ", ts) + } + + e := d.client.Del(ts.Name).Err() + + if glog.V(3) { + glog.Info("DeleteMapAll: End: ", " e: ", e) + } + + return e +} +// For Testing only. Do Not Use!!! ============================== + + diff --git a/translib/db/subscribe.go b/translib/db/subscribe.go new file mode 100644 index 000000000..61802411c --- /dev/null +++ b/translib/db/subscribe.go @@ -0,0 +1,275 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +/* +Package db implements a wrapper over the go-redis/redis. +*/ +package db + +import ( + // "fmt" + // "strconv" + + // "reflect" + "errors" + "strings" + + // "github.com/go-redis/redis" + "github.com/golang/glog" + // "github.com/Azure/sonic-mgmt-common/cvl" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" +) + +// SKey is (TableSpec, Key, []SEvent) 3-tuples to be watched in a Transaction. +type SKey struct { + Ts *TableSpec + Key *Key + SEMap map[SEvent]bool // nil map indicates subscribe to all +} + +type SEvent int + +const ( + SEventNone SEvent = iota // No Op + SEventHSet // HSET, HMSET, and its variants + SEventHDel // HDEL, also SEventDel generated, if HASH is becomes empty + SEventDel // DEL, & also if key gets deleted (empty HASH, expire,..) + SEventOther // Some other command not covered above. + + // The below two are always sent regardless of SEMap. + SEventClose // Close requested due to Unsubscribe() called. + SEventErr // Error condition. Call Unsubscribe, after return. +) + +var redisPayload2sEventMap map[string]SEvent = map[string]SEvent { + "" : SEventNone, + "hset" : SEventHSet, + "hdel" : SEventHDel, + "del" : SEventDel, +} + + +func init() { + // Optimization: Start the goroutine that is scanning the SubscribeDB + // channels. Instead of one goroutine per Subscribe. +} + + +// HFunc gives the name of the table, and other per-table customizations. +type HFunc func( *DB, *SKey, *Key, SEvent) (error) + + +// SubscribeDB is the factory method to create a subscription to the DB. +// The returned instance can only be used for Subscription. +func SubscribeDB(opt Options, skeys []*SKey, handler HFunc) (*DB, error) { + + if glog.V(3) { + glog.Info("SubscribeDB: Begin: opt: ", opt, + " skeys: ", skeys, " handler: ", handler) + } + + patterns := make([]string, 0, len(skeys)) + patMap := make(map[string]([]int), len(skeys)) + var s string + + // NewDB + d , e := NewDB(opt) + + if d.client == nil { + goto SubscribeDBExit + } + + // Make sure that the DB is configured for key space notifications + // Optimize with LUA scripts to atomically add "Kgshxe". + s, e = d.client.ConfigSet("notify-keyspace-events", "AKE").Result() + + if e != nil { + glog.Error("SubscribeDB: ConfigSet(): e: ", e, " s: ", s) + goto SubscribeDBExit + } + + for i := 0 ; i < len(skeys); i++ { + pattern := d.key2redisChannel(skeys[i].Ts, *(skeys[i].Key)) + if _,present := patMap[pattern] ; ! present { + patMap[pattern] = make([]int, 0, 5) + patterns = append(patterns, pattern) + } + patMap[pattern] = append(patMap[pattern], i) + + } + + glog.Info("SubscribeDB: patterns: ", patterns) + + d.sPubSub = d.client.PSubscribe(patterns[:]...) + + if d.sPubSub == nil { + glog.Error("SubscribeDB: PSubscribe() nil: pats: ", patterns) + e = tlerr.TranslibDBSubscribeFail { } + goto SubscribeDBExit + } + + // Wait for confirmation, of channel creation + _, e = d.sPubSub.Receive() + + if e != nil { + glog.Error("SubscribeDB: Receive() fails: e: ", e) + e = tlerr.TranslibDBSubscribeFail { } + goto SubscribeDBExit + } + + + // Start a goroutine to read messages and call handler. + go func() { + for msg := range d.sPubSub.Channel() { + if glog.V(4) { + glog.Info("SubscribeDB: msg: ", msg) + } + + // Should this be a goroutine, in case each notification CB + // takes a long time to run ? + for _, skeyIndex := range patMap[msg.Pattern] { + skey := skeys[skeyIndex] + key := d.redisChannel2key(skey.Ts, msg.Channel) + sevent := d.redisPayload2sEvent(msg.Payload) + + if len(skey.SEMap) == 0 || skey.SEMap[sevent] { + + if glog.V(2) { + glog.Info("SubscribeDB: handler( ", + &d, ", ", skey, ", ", key, ", ", sevent, " )") + } + + handler(d, skey, &key, sevent) + } + } + } + + // Send the Close|Err notification. + var sEvent = SEventClose + if d.sCIP == false { + sEvent = SEventErr + } + glog.Info("SubscribeDB: SEventClose|Err: ", sEvent) + handler(d, & SKey{}, & Key {}, sEvent) + } () + + +SubscribeDBExit: + + if e != nil { + if d.sPubSub != nil { + d.sPubSub.Close() + } + + if d.client != nil { + d.DeleteDB() + d.client = nil + } + d = nil + } + + if glog.V(3) { + glog.Info("SubscribeDB: End: d: ", d, " e: ", e) + } + + return d, e +} + +// UnsubscribeDB is used to close a DB subscription +func (d * DB) UnsubscribeDB() error { + + var e error = nil + + if glog.V(3) { + glog.Info("UnsubscribeDB: d:", d) + } + + if d.sCIP { + glog.Error("UnsubscribeDB: Close in Progress") + e = errors.New("UnsubscribeDB: Close in Progress") + goto UnsubscribeDBExit + } + + // Mark close in progress. + d.sCIP = true; + + // Do the close, ch gets closed too. + d.sPubSub.Close() + + // Wait for the goroutine to complete ? TBD + // Should not this happen because of the range statement on ch? + + // Close the DB + d.DeleteDB() + +UnsubscribeDBExit: + + if glog.V(3) { + glog.Info("UnsubscribeDB: End: d: ", d, " e: ", e) + } + + return e +} + + +func (d *DB) key2redisChannel(ts *TableSpec, key Key) string { + + if glog.V(5) { + glog.Info("key2redisChannel: ", *ts, " key: " + key.String()) + } + + return "__keyspace@" + (d.Opts.DBNo).String() + "__:" + d.key2redis(ts, key) +} + +func (d *DB) redisChannel2key(ts *TableSpec, redisChannel string) Key { + + if glog.V(5) { + glog.Info("redisChannel2key: ", *ts, " redisChannel: " + redisChannel) + } + + splitRedisKey := strings.SplitN(redisChannel, ":", 2) + + if len(splitRedisKey) > 1 { + return d.redis2key(ts, splitRedisKey[1]) + } + + glog.Warning("redisChannel2key: Missing key: redisChannel: ", redisChannel) + + return Key{} +} + +func (d *DB) redisPayload2sEvent(redisPayload string) SEvent { + + if glog.V(5) { + glog.Info("redisPayload2sEvent: ", redisPayload) + } + + sEvent := redisPayload2sEventMap[redisPayload] + + if sEvent == 0 { + sEvent = SEventOther + } + + if glog.V(3) { + glog.Info("redisPayload2sEvent: ", sEvent) + } + + return sEvent +} + diff --git a/translib/db/test/arloIssue29.go b/translib/db/test/arloIssue29.go new file mode 100644 index 000000000..eb6dadc03 --- /dev/null +++ b/translib/db/test/arloIssue29.go @@ -0,0 +1,85 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// 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. // +// // +//////////////////////////////////////////////////////////////////////////////// + +/* +UT for +https://github.com/project-arlo/sonic-mgmt-framework/issues/29 +*/ + +package main + +import ( + "fmt" + // "errors" + "flag" + "github.com/golang/glog" + "github.com/Azure/sonic-mgmt-common/translib/db" + // "time" + // "github.com/Azure/sonic-mgmt-common/translib/tlerr" +) + +func main() { + var avalue db.Value + var akey db.Key + var e error + + defer glog.Flush() + + flag.Parse() + + fmt.Println("https://github.com/project-arlo/sonic-mgmt-framework/issues/29") + fmt.Println("Creating the DB ==============") + d,_ := db.NewDB(db.Options { + DBNo : db.ApplDB, + InitIndicator : "", + TableNameSeparator: ":", + KeySeparator : ":", + }) + + tsi := db.TableSpec { Name: "INTF_TABLE", CompCt: 2 } + + ca := make([]string, 2, 2) + + fmt.Println("Testing SetEntry ==============") + ca[0] = "Ethernet20" + ca[1] = "a::b/64" + akey = db.Key { Comp: ca} + avalue = db.Value { Field: map[string]string { + "scope" : "global", + "family" : "IPv4", + } } + + e = d.SetEntry(&tsi, akey, avalue) + if e != nil { + fmt.Println("SetEntry() ERROR: e: ", e) + return + } + + fmt.Println("Testing GetEntry ==============") + + avalue, e = d.GetEntry(&tsi, akey) + if e != nil { + fmt.Println("GetEntry() ERROR: e: ", e) + return + } + + fmt.Println("ts: ", tsi, " ", akey, ": ", avalue) + + d.DeleteDB() +} diff --git a/translib/db/test/testdb.go b/translib/db/test/testdb.go new file mode 100644 index 000000000..2ef9c2be0 --- /dev/null +++ b/translib/db/test/testdb.go @@ -0,0 +1,163 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package main + +import ( + "fmt" + // "errors" + "flag" + "github.com/golang/glog" + "github.com/Azure/sonic-mgmt-common/translib/db" + "time" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" +) + +func main() { + var avalue,rvalue db.Value + var akey,rkey db.Key + var e error + + defer glog.Flush() + + flag.Parse() + + fmt.Println("Creating the DB ==============") + d,_ := db.NewDB(db.Options { + DBNo : db.ConfigDB, + InitIndicator : "CONFIG_DB_INITIALIZED", + TableNameSeparator: "|", + KeySeparator : "|", + }) + +// fmt.Println("key: CONFIG_DB_INITIALIZED value: ", +// d.Client.Get("CONFIG_DB_INITIALIZED").String()) + + tsa := db.TableSpec { Name: "ACL_TABLE" } + tsr := db.TableSpec { Name: "ACL_RULE" } + + ca := make([]string, 1, 1) + + fmt.Println("Testing GetEntry error ==============") + ca[0] = "MyACL1_ACL_IPVNOTEXIST" + akey = db.Key { Comp: ca} + avalue, e = d.GetEntry(&tsa, akey) + fmt.Println("ts: ", tsa, " ", akey, ": ", avalue, " error: ", e) + if _, ok := e.(tlerr.TranslibRedisClientEntryNotExist) ; ok { + fmt.Println("Type is TranslibRedisClientEntryNotExist") + } + + + fmt.Println("Testing NoTransaction SetEntry ==============") + ca[0] = "MyACL1_ACL_IPV4" + akey = db.Key { Comp: ca} + avalue = db.Value { map[string]string {"ports@":"Ethernet0","type":"MIRROR" }} + + d.SetEntry(&tsa, akey, avalue) + + fmt.Println("Testing GetEntry ==============") + avalue, _ = d.GetEntry(&tsa, akey) + fmt.Println("ts: ", tsa, " ", akey, ": ", avalue) + + fmt.Println("Testing GetKeys ==============") + keys, _ := d.GetKeys(&tsa); + fmt.Println("ts: ", tsa, " keys: ", keys) + + fmt.Println("Testing NoTransaction DeleteEntry ==============") + akey = db.Key { Comp: ca} + + d.DeleteEntry(&tsa, akey) + + avalue, e = d.GetEntry(&tsa, akey) + if e == nil { + fmt.Println("!!! ts: ", tsa, " ", akey, ": ", avalue) + } + + fmt.Println("Testing 2 more ACLs ==============") + ca[0] = "MyACL2_ACL_IPV4" + avalue = db.Value { map[string]string {"ports@":"Ethernet0","type":"MIRROR" }} + d.SetEntry(&tsa, akey, avalue) + + ca[0] = "MyACL3_ACL_IPV4" + d.SetEntry(&tsa, akey, avalue) + + ta, _ := d.GetTable(&tsa) + fmt.Println("ts: ", tsa, " table: ", ta) + + tr, _ := d.GetTable(&tsr) + fmt.Println("ts: ", tsr, " table: ", tr) + + fmt.Println("Testing Transaction =================") + rkey = db.Key { Comp: []string { "MyACL2_ACL_IPV4", "RULE_1" }} + rvalue = db.Value { Field: map[string]string { + "priority" : "0", + "packet_action" : "DROP", + }, + } + +// d.StartTx([]db.WatchKeys { {Ts: &tsr, Key: &rkey} }) + d.StartTx([]db.WatchKeys {{Ts: &tsr, Key: &rkey} }, + []*db.TableSpec { &tsr, &tsa}) + + fmt.Println("Sleeping 5...") + time.Sleep(5 * time.Second) + + d.SetEntry( &tsr, rkey, rvalue) + + e = d.CommitTx() + if e != nil { + fmt.Println("Transaction Failed ======= e: ", e) + } + + + fmt.Println("Testing AbortTx =================") +// d.StartTx([]db.WatchKeys { {Ts: &tsr, Key: &rkey} }) + d.StartTx([]db.WatchKeys {}, []*db.TableSpec { &tsr, &tsa}) + d.DeleteEntry( &tsa, rkey) + d.AbortTx() + avalue, e = d.GetEntry(&tsr, rkey) + fmt.Println("ts: ", tsr, " ", akey, ": ", avalue) + + fmt.Println("Testing DeleteKeys =================") + d.DeleteKeys(&tsr, db.Key { Comp: []string {"ToBeDeletedACLs*"} }) + + fmt.Println("Testing GetTable") + tr, _ = d.GetTable(&tsr) + fmt.Println("ts: ", tsr, " table: ", tr) + + +// d.DeleteTable(&ts) + + fmt.Println("Testing Tables2TableSpecs =================") + var tables []string + tables = []string { "ACL_TABLE", "ACL_RULE" } + fmt.Println("Tables: ", tables) + fmt.Println("TableSpecs: ") + for _, tsi := range db.Tables2TableSpecs(tables) { + fmt.Println(" ", *tsi) + } + + fmt.Println("Empty TableSpecs: ") + for _, tsi := range db.Tables2TableSpecs([]string { } ) { + fmt.Println(" ", *tsi) + } + + + d.DeleteDB() +} diff --git a/translib/db/test/testmap.go b/translib/db/test/testmap.go new file mode 100644 index 000000000..78a22bcc3 --- /dev/null +++ b/translib/db/test/testmap.go @@ -0,0 +1,102 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package main + +import ( + "fmt" + // "errors" + "flag" + "github.com/golang/glog" + "github.com/Azure/sonic-mgmt-common/translib/db" + // "time" + // "github.com/Azure/sonic-mgmt-common/translib/tlerr" +) + + +func handler(d *db.DB, skey *db.SKey, key *db.Key, event db.SEvent) error { + fmt.Println("***handler: d: ", d, " skey: ", *skey, " key: ", *key, + " event: ", event) + return nil +} + + +func main() { + defer glog.Flush() + + flag.Parse() + + tsc := db.TableSpec { Name: "COUNTERS_PORT_NAME_MAP" } + + fmt.Println("Creating the SubscribeDB ==============") + d,e := db.NewDB(db.Options { + DBNo : db.CountersDB, + InitIndicator : "", + TableNameSeparator: ":", + KeySeparator : ":", + }) + + if e != nil { + fmt.Println("NewDB() returns error e: ", e) + } + + fmt.Println("Setting Some Maps ==============") + d.SetMap(&tsc, "Ethernet2", "oid:0x1000000000002") + d.SetMap(&tsc, "Ethernet5", "oid:0x1000000000005") + d.SetMap(&tsc, "Ethernet3", "oid:0x1000000000003") + + fmt.Println("GetMapAll ==============") + v, e := d.GetMapAll(&tsc) + if e != nil { + fmt.Println("GetMapAll() returns error e: ", e) + } + fmt.Println("v: ", v) + + fmt.Println("GetMap ==============") + r2, e := d.GetMap(&tsc, "Ethernet2") + if e != nil { + fmt.Println("GetMap() returns error e: ", e) + } + r5, e := d.GetMap(&tsc, "Ethernet5") + if e != nil { + fmt.Println("GetMap() returns error e: ", e) + } + r3, e := d.GetMap(&tsc, "Ethernet3") + if e != nil { + fmt.Println("GetMap() returns error e: ", e) + } + + fmt.Println("r2, r5, r3", r2, r5, r3) + + + fmt.Println("GetMap NotExist mapKey ==============") + rN, e := d.GetMap(&tsc, "EthernetN") + if e == nil { + fmt.Println("GetMap() NotExist mapKey returns nil !!! ", rN) + } + + vN, e := d.GetMapAll(& db.TableSpec { Name: "NOTEXITMAP" } ) + if e == nil { + fmt.Println("GetMapAll() NotExist returns nil !!! ", vN) + } + + d.DeleteMapAll(&tsc) + + d.DeleteDB() +} diff --git a/translib/db/test/testsubscribe.go b/translib/db/test/testsubscribe.go new file mode 100644 index 000000000..1272bd819 --- /dev/null +++ b/translib/db/test/testsubscribe.go @@ -0,0 +1,88 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package main + +import ( + "fmt" + // "errors" + "flag" + "github.com/golang/glog" + "github.com/Azure/sonic-mgmt-common/translib/db" + "time" + // "github.com/Azure/sonic-mgmt-common/translib/tlerr" +) + + +func handler(d *db.DB, skey *db.SKey, key *db.Key, event db.SEvent) error { + fmt.Println("***handler: d: ", d, " skey: ", *skey, " key: ", *key, + " event: ", event) + return nil +} + + +func main() { + // var avalue,rvalue db.Value + var akey db.Key + // var rkey db.Key + // var e error + + defer glog.Flush() + + flag.Parse() + + tsa := db.TableSpec { Name: "ACL_TABLE" } + // tsr := db.TableSpec { Name: "ACL_RULE" } + + ca := make([]string, 1, 1) + ca[0] = "MyACL1_ACL_IPVNOTEXIST*" + akey = db.Key { Comp: ca} + var skeys [] *db.SKey = make([]*db.SKey, 1) + skeys[0] = & (db.SKey { Ts: &tsa, Key: &akey, + SEMap: map[db.SEvent]bool { + db.SEventHSet: true, + db.SEventHDel: true, + db.SEventDel: true, + }}) + + fmt.Println("Creating the SubscribeDB ==============") + d,e := db.SubscribeDB(db.Options { + DBNo : db.ConfigDB, + InitIndicator : "CONFIG_DB_INITIALIZED", + TableNameSeparator: "|", + KeySeparator : "|", + }, skeys, handler) + + if e != nil { + fmt.Println("Subscribe() returns error e: ", e) + } + + fmt.Println("Sleeping 15 ==============") + time.Sleep(15 * time.Second) + + + fmt.Println("Testing UnsubscribeDB ==============") + + d.UnsubscribeDB() + + fmt.Println("Sleeping 5 ==============") + time.Sleep(5 * time.Second) + + +} diff --git a/translib/intf_app.go b/translib/intf_app.go new file mode 100644 index 000000000..94b9b1abf --- /dev/null +++ b/translib/intf_app.go @@ -0,0 +1,1291 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright 2019 Dell, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +////////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "errors" + "fmt" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" + "net" + "reflect" + "regexp" + "strconv" + "strings" + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" + "unsafe" +) + +type reqType int + +const ( + opCreate reqType = iota + 1 + opDelete + opUpdate +) + +type dbEntry struct { + op reqType + entry db.Value +} + +const ( + PORT = "PORT" + PORT_INDEX = "index" + PORT_MTU = "mtu" + PORT_ADMIN_STATUS = "admin_status" + PORT_SPEED = "speed" + PORT_DESC = "description" + PORT_OPER_STATUS = "oper_status" +) + +type Table int + +const ( + IF_TABLE_MAP Table = iota + PORT_STAT_MAP +) + +type IntfApp struct { + path *PathInfo + reqData []byte + ygotRoot *ygot.GoStruct + ygotTarget *interface{} + + respJSON interface{} + allIpKeys []db.Key + + appDB *db.DB + countersDB *db.DB + + ifTableMap map[string]dbEntry + ifIPTableMap map[string]map[string]dbEntry + portOidMap dbEntry + portStatMap map[string]dbEntry + + portTs *db.TableSpec + portTblTs *db.TableSpec + intfIPTs *db.TableSpec + intfIPTblTs *db.TableSpec + intfCountrTblTs *db.TableSpec + portOidCountrTblTs *db.TableSpec +} + +func init() { + log.Info("Init called for INTF module") + err := register("/openconfig-interfaces:interfaces", + &appInfo{appType: reflect.TypeOf(IntfApp{}), + ygotRootType: reflect.TypeOf(ocbinds.OpenconfigInterfaces_Interfaces{}), + isNative: false}) + if err != nil { + log.Fatal("Register INTF app module with App Interface failed with error=", err) + } + + err = addModel(&ModelData{Name: "openconfig-interfaces", + Org: "OpenConfig working group", + Ver: "1.0.2"}) + if err != nil { + log.Fatal("Adding model data to appinterface failed with error=", err) + } +} + +func (app *IntfApp) initialize(data appData) { + log.Info("initialize:if:path =", data.path) + + app.path = NewPathInfo(data.path) + app.reqData = data.payload + app.ygotRoot = data.ygotRoot + app.ygotTarget = data.ygotTarget + + app.portTs = &db.TableSpec{Name: "PORT"} + app.portTblTs = &db.TableSpec{Name: "PORT_TABLE"} + app.intfIPTs = &db.TableSpec{Name: "INTERFACE"} + app.intfIPTblTs = &db.TableSpec{Name: "INTF_TABLE", CompCt: 2} + app.intfCountrTblTs = &db.TableSpec{Name: "COUNTERS"} + app.portOidCountrTblTs = &db.TableSpec{Name: "COUNTERS_PORT_NAME_MAP"} + + app.ifTableMap = make(map[string]dbEntry) + app.ifIPTableMap = make(map[string]map[string]dbEntry) + app.portStatMap = make(map[string]dbEntry) +} + +func (app *IntfApp) getAppRootObject() *ocbinds.OpenconfigInterfaces_Interfaces { + deviceObj := (*app.ygotRoot).(*ocbinds.Device) + return deviceObj.Interfaces +} + +func (app *IntfApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateCreate:intf:path =", app.path) + + err = errors.New("Not implemented") + return keys, err +} + +func (app *IntfApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + + log.Info("translateUpdate:intf:path =", app.path) + + keys, err = app.translateCommon(d, opUpdate) + + if err != nil { + log.Info("Something wrong:=", err) + } + + return keys, err +} + +func (app *IntfApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateReplace:intf:path =", app.path) + err = errors.New("Not implemented") + return keys, err +} + +func (app *IntfApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + pathInfo := app.path + + log.Infof("Received Delete for path %s; vars=%v", pathInfo.Template, pathInfo.Vars) + + intfObj := app.getAppRootObject() + + targetUriPath, err := getYangPathFromUri(app.path.Path) + log.Info("uripath:=", targetUriPath) + log.Info("err:=", err) + + if intfObj.Interface != nil && len(intfObj.Interface) > 0 { + log.Info("len:=", len(intfObj.Interface)) + for ifKey, _ := range intfObj.Interface { + log.Info("Name:=", ifKey) + intf := intfObj.Interface[ifKey] + + if intf.Subinterfaces == nil { + continue + } + subIf := intf.Subinterfaces.Subinterface[0] + if subIf != nil { + if subIf.Ipv4 != nil && subIf.Ipv4.Addresses != nil { + for ip, _ := range subIf.Ipv4.Addresses.Address { + addr := subIf.Ipv4.Addresses.Address[ip] + if addr != nil { + ipAddr := addr.Ip + log.Info("IPv4 address = ", *ipAddr) + if !validIPv4(*ipAddr) { + errStr := "Invalid IPv4 address " + *ipAddr + ipValidErr := tlerr.InvalidArgsError{Format: errStr} + return keys, ipValidErr + } + err = app.validateIp(d, ifKey, *ipAddr) + if err != nil { + errStr := "Invalid IPv4 address " + *ipAddr + ipValidErr := tlerr.InvalidArgsError{Format: errStr} + return keys, ipValidErr + } + } + } + } + if subIf.Ipv6 != nil && subIf.Ipv6.Addresses != nil { + for ip, _ := range subIf.Ipv6.Addresses.Address { + addr := subIf.Ipv6.Addresses.Address[ip] + if addr != nil { + ipAddr := addr.Ip + log.Info("IPv6 address = ", *ipAddr) + if !validIPv6(*ipAddr) { + errStr := "Invalid IPv6 address " + *ipAddr + ipValidErr := tlerr.InvalidArgsError{Format: errStr} + return keys, ipValidErr + } + err = app.validateIp(d, ifKey, *ipAddr) + if err != nil { + errStr := "Invalid IPv6 address:" + *ipAddr + ipValidErr := tlerr.InvalidArgsError{Format: errStr} + return keys, ipValidErr + } + } + } + } + } else { + err = errors.New("Only subinterface index 0 is supported") + return keys, err + } + } + } else { + err = errors.New("Not implemented") + } + return keys, err +} + +func (app *IntfApp) translateGet(dbs [db.MaxDB]*db.DB) error { + var err error + log.Info("translateGet:intf:path =", app.path) + return err +} + +func (app *IntfApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { + app.appDB = dbs[db.ApplDB] + pathInfo := NewPathInfo(path) + notifInfo := notificationInfo{dbno: db.ApplDB} + notSupported := tlerr.NotSupportedError{Format: "Subscribe not supported", Path: path} + + if isSubtreeRequest(pathInfo.Template, "/openconfig-interfaces:interfaces") { + if pathInfo.HasSuffix("/interface{}") || + pathInfo.HasSuffix("/config") || + pathInfo.HasSuffix("/state") { + log.Errorf("Subscribe not supported for %s!", pathInfo.Template) + return nil, nil, notSupported + } + ifKey := pathInfo.Var("name") + if len(ifKey) == 0 { + return nil, nil, errors.New("ifKey given is empty!") + } + log.Info("Interface name = ", ifKey) + err := app.validateInterface(app.appDB, ifKey, db.Key{Comp: []string{ifKey}}) + if err != nil { + return nil, nil, err + } + if pathInfo.HasSuffix("/state/oper-status") { + notifInfo.table = db.TableSpec{Name: "PORT_TABLE"} + notifInfo.key = asKey(ifKey) + notifInfo.needCache = true + return ¬ificationOpts{pType: OnChange}, ¬ifInfo, nil + } + } + return nil, nil, notSupported +} + +func (app *IntfApp) processCreate(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + log.Info("processCreate:intf:path =", app.path) + log.Info("ProcessCreate: Target Type is " + reflect.TypeOf(*app.ygotTarget).Elem().Name()) + + err = errors.New("Not implemented") + return resp, err +} + +func (app *IntfApp) processUpdate(d *db.DB) (SetResponse, error) { + + log.Infof("Calling processCommon()") + + resp, err := app.processCommon(d) + return resp, err +} + +func (app *IntfApp) processReplace(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + log.Info("processReplace:intf:path =", app.path) + err = errors.New("Not implemented") + return resp, err +} + +func (app *IntfApp) processDelete(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + log.Info("processDelete:intf:path =", app.path) + + if len(app.ifIPTableMap) == 0 { + return resp, err + } + for ifKey, entrylist := range app.ifIPTableMap { + for ip, _ := range entrylist { + err = d.DeleteEntry(app.intfIPTs, db.Key{Comp: []string{ifKey, ip}}) + log.Infof("Deleted IP : %s for Interface : %s", ip, ifKey) + } + } + return resp, err +} + +/* Note : Registration already happened, followed by filling the internal DS and filling the JSON */ +func (app *IntfApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { + + var err error + var payload []byte + pathInfo := app.path + + log.Infof("Received GET for path %s; template: %s vars=%v", pathInfo.Path, pathInfo.Template, pathInfo.Vars) + app.appDB = dbs[db.ApplDB] + app.countersDB = dbs[db.CountersDB] + + intfObj := app.getAppRootObject() + + targetUriPath, err := getYangPathFromUri(app.path.Path) + log.Info("URI Path = ", targetUriPath) + + if isSubtreeRequest(targetUriPath, "/openconfig-interfaces:interfaces/interface") { + /* Request for a specific interface */ + if intfObj.Interface != nil && len(intfObj.Interface) > 0 { + /* Interface name is the key */ + for ifKey, _ := range intfObj.Interface { + log.Info("Interface Name = ", ifKey) + ifInfo := intfObj.Interface[ifKey] + /* Filling Interface Info to internal DS */ + err = app.convertDBIntfInfoToInternal(app.appDB, ifKey, db.Key{Comp: []string{ifKey}}) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + /*Check if the request is for a specific attribute in Interfaces state container*/ + oc_val := &ocbinds.OpenconfigInterfaces_Interfaces_Interface_State{} + ok, e := app.getSpecificAttr(targetUriPath, ifKey, oc_val) + if ok { + if e != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, e + } + + payload, err = dumpIetfJson(oc_val, false) + if err == nil { + return GetResponse{Payload: payload}, err + } else { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + } + + /* Filling the counter Info to internal DS */ + err = app.getPortOidMapForCounters(app.countersDB) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + err = app.convertDBIntfCounterInfoToInternal(app.countersDB, ifKey) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + /*Check if the request is for a specific attribute in Interfaces state COUNTERS container*/ + counter_val := &ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_Counters{} + ok, e = app.getSpecificCounterAttr(targetUriPath, ifKey, counter_val) + if ok { + if e != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, e + } + + payload, err = dumpIetfJson(counter_val, false) + if err == nil { + return GetResponse{Payload: payload}, err + } else { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + } + + /* Filling Interface IP info to internal DS */ + err = app.convertDBIntfIPInfoToInternal(app.appDB, ifKey) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + + /* Filling the tree with the info we have in Internal DS */ + ygot.BuildEmptyTree(ifInfo) + if *app.ygotTarget == ifInfo.State { + ygot.BuildEmptyTree(ifInfo.State) + } + app.convertInternalToOCIntfInfo(&ifKey, ifInfo) + if *app.ygotTarget == ifInfo { + payload, err = dumpIetfJson(intfObj, false) + } else { + dummyifInfo := &ocbinds.OpenconfigInterfaces_Interfaces_Interface{} + if *app.ygotTarget == ifInfo.Config { + dummyifInfo.Config = ifInfo.Config + payload, err = dumpIetfJson(dummyifInfo, false) + } else if *app.ygotTarget == ifInfo.State { + dummyifInfo.State = ifInfo.State + payload, err = dumpIetfJson(dummyifInfo, false) + } else { + log.Info("Not supported get type!") + err = errors.New("Requested get-type not supported!") + } + } + } + } + return GetResponse{Payload: payload}, err + } + + /* Get all Interfaces */ + if isSubtreeRequest(targetUriPath, "/openconfig-interfaces:interfaces") { + log.Info("Get all Interfaces request!") + /* Filling Interface Info to internal DS */ + err = app.convertDBIntfInfoToInternal(app.appDB, "", db.Key{}) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + /* Filling Interface IP info to internal DS */ + err = app.convertDBIntfIPInfoToInternal(app.appDB, "") + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + /* Filling the counter Info to internal DS */ + err = app.getPortOidMapForCounters(app.countersDB) + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + err = app.convertDBIntfCounterInfoToInternal(app.countersDB, "") + if err != nil { + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + ygot.BuildEmptyTree(intfObj) + for ifKey, _ := range app.ifTableMap { + log.Info("If Key = ", ifKey) + ifInfo, err := intfObj.NewInterface(ifKey) + if err != nil { + log.Errorf("Creation of interface subtree for %s failed!", ifKey) + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + ygot.BuildEmptyTree(ifInfo) + app.convertInternalToOCIntfInfo(&ifKey, ifInfo) + } + if *app.ygotTarget == intfObj { + payload, err = dumpIetfJson((*app.ygotRoot).(*ocbinds.Device), true) + } else { + log.Error("Wrong request!") + } + } + return GetResponse{Payload: payload}, err +} + +/* Checking IP adderss is v4 */ +func validIPv4(ipAddress string) bool { + ipAddress = strings.Trim(ipAddress, " ") + + re, _ := regexp.Compile(`^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`) + if re.MatchString(ipAddress) { + return true + } + return false +} + +/* Checking IP address is v6 */ +func validIPv6(ip6Address string) bool { + ip6Address = strings.Trim(ip6Address, " ") + re, _ := regexp.Compile(`(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`) + if re.MatchString(ip6Address) { + return true + } + return false +} + +func (app *IntfApp) doGetAllIpKeys(d *db.DB, dbSpec *db.TableSpec) ([]db.Key, error) { + + var keys []db.Key + + intfTable, err := d.GetTable(dbSpec) + if err != nil { + return keys, err + } + + keys, err = intfTable.GetKeys() + log.Infof("Found %d INTF table keys", len(keys)) + return keys, err +} + +func (app *IntfApp) getSpecificAttr(targetUriPath string, ifKey string, oc_val *ocbinds.OpenconfigInterfaces_Interfaces_Interface_State) (bool, error) { + switch targetUriPath { + case "/openconfig-interfaces:interfaces/interface/state/oper-status": + val, e := app.getIntfAttr(ifKey, PORT_OPER_STATUS, IF_TABLE_MAP) + if len(val) > 0 { + switch val { + case "up": + oc_val.OperStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_OperStatus_UP + case "down": + oc_val.OperStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_OperStatus_DOWN + default: + oc_val.OperStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_OperStatus_UNSET + } + return true, nil + } else { + return true, e + } + case "/openconfig-interfaces:interfaces/interface/state/admin-status": + val, e := app.getIntfAttr(ifKey, PORT_ADMIN_STATUS, IF_TABLE_MAP) + if len(val) > 0 { + switch val { + case "up": + oc_val.AdminStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus_UP + case "down": + oc_val.AdminStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus_DOWN + default: + oc_val.AdminStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus_UNSET + } + return true, nil + } else { + return true, e + } + case "/openconfig-interfaces:interfaces/interface/state/mtu": + val, e := app.getIntfAttr(ifKey, PORT_MTU, IF_TABLE_MAP) + if len(val) > 0 { + v, e := strconv.ParseUint(val, 10, 16) + if e == nil { + oc_val.Mtu = (*uint16)(unsafe.Pointer(&v)) + return true, nil + } + } + return true, e + case "/openconfig-interfaces:interfaces/interface/state/ifindex": + val, e := app.getIntfAttr(ifKey, PORT_INDEX, IF_TABLE_MAP) + if len(val) > 0 { + v, e := strconv.ParseUint(val, 10, 32) + if e == nil { + oc_val.Ifindex = (*uint32)(unsafe.Pointer(&v)) + return true, nil + } + } + return true, e + case "/openconfig-interfaces:interfaces/interface/state/description": + val, e := app.getIntfAttr(ifKey, PORT_DESC, IF_TABLE_MAP) + if e == nil { + oc_val.Description = &val + return true, nil + } + return true, e + + default: + log.Infof(targetUriPath + " - Not an interface state attribute") + } + return false, nil +} + +func (app *IntfApp) getSpecificCounterAttr(targetUriPath string, ifKey string, counter_val *ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_Counters) (bool, error) { + + var e error + + switch targetUriPath { + case "/openconfig-interfaces:interfaces/interface/state/counters/in-octets": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_OCTETS", &counter_val.InOctets) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/in-unicast-pkts": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_UCAST_PKTS", &counter_val.InUnicastPkts) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/in-broadcast-pkts": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_BROADCAST_PKTS", &counter_val.InBroadcastPkts) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/in-multicast-pkts": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_MULTICAST_PKTS", &counter_val.InMulticastPkts) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/in-errors": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_ERRORS", &counter_val.InErrors) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/in-discards": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_DISCARDS", &counter_val.InDiscards) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/in-pkts": + var inNonUCastPkt, inUCastPkt *uint64 + var in_pkts uint64 + + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_NON_UCAST_PKTS", &inNonUCastPkt) + if e == nil { + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_IN_UCAST_PKTS", &inUCastPkt) + if e != nil { + return true, e + } + in_pkts = *inUCastPkt + *inNonUCastPkt + counter_val.InPkts = &in_pkts + return true, e + } else { + return true, e + } + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-octets": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_OCTETS", &counter_val.OutOctets) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-unicast-pkts": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_UCAST_PKTS", &counter_val.OutUnicastPkts) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-broadcast-pkts": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_BROADCAST_PKTS", &counter_val.OutBroadcastPkts) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-multicast-pkts": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_MULTICAST_PKTS", &counter_val.OutMulticastPkts) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-errors": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_ERRORS", &counter_val.OutErrors) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-discards": + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_DISCARDS", &counter_val.OutDiscards) + return true, e + + case "/openconfig-interfaces:interfaces/interface/state/counters/out-pkts": + var outNonUCastPkt, outUCastPkt *uint64 + var out_pkts uint64 + + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_NON_UCAST_PKTS", &outNonUCastPkt) + if e == nil { + e = app.getCounters(ifKey, "SAI_PORT_STAT_IF_OUT_UCAST_PKTS", &outUCastPkt) + if e != nil { + return true, e + } + out_pkts = *outUCastPkt + *outNonUCastPkt + counter_val.OutPkts = &out_pkts + return true, e + } else { + return true, e + } + + default: + log.Infof(targetUriPath + " - Not an interface state counter attribute") + } + return false, nil +} + +func (app *IntfApp) getCounters(ifKey string, attr string, counter_val **uint64) error { + val, e := app.getIntfAttr(ifKey, attr, PORT_STAT_MAP) + if len(val) > 0 { + v, e := strconv.ParseUint(val, 10, 64) + if e == nil { + *counter_val = &v + return nil + } + } + return e +} + +func (app *IntfApp) getIntfAttr(ifName string, attr string, table Table) (string, error) { + + var ok bool = false + var entry dbEntry + + if table == IF_TABLE_MAP { + entry, ok = app.ifTableMap[ifName] + } else if table == PORT_STAT_MAP { + entry, ok = app.portStatMap[ifName] + } else { + return "", errors.New("Unsupported table") + } + + if ok { + ifData := entry.entry + + if val, ok := ifData.Field[attr]; ok { + return val, nil + } + } + return "", errors.New("Attr " + attr + "doesn't exist in IF table Map!") +} + +/*********** Translation Helper fn to convert DB Interface info to Internal DS ***********/ +func (app *IntfApp) getPortOidMapForCounters(dbCl *db.DB) error { + var err error + ifCountInfo, err := dbCl.GetMapAll(app.portOidCountrTblTs) + if err != nil { + log.Error("Port-OID (Counters) get for all the interfaces failed!") + return err + } + if ifCountInfo.IsPopulated() { + app.portOidMap.entry = ifCountInfo + } else { + return errors.New("Get for OID info from all the interfaces from Counters DB failed!") + } + return err +} + +func (app *IntfApp) convertDBIntfCounterInfoToInternal(dbCl *db.DB, ifKey string) error { + var err error + + if len(ifKey) > 0 { + oid := app.portOidMap.entry.Field[ifKey] + log.Infof("OID : %s received for Interface : %s", oid, ifKey) + + /* Get the statistics for the port */ + var ifStatKey db.Key + ifStatKey.Comp = []string{oid} + + ifStatInfo, err := dbCl.GetEntry(app.intfCountrTblTs, ifStatKey) + if err != nil { + log.Infof("Fetching port-stat for port : %s failed!", ifKey) + return err + } + app.portStatMap[ifKey] = dbEntry{entry: ifStatInfo} + } else { + for ifKey, _ := range app.ifTableMap { + app.convertDBIntfCounterInfoToInternal(dbCl, ifKey) + } + } + return err +} + +func (app *IntfApp) validateInterface(dbCl *db.DB, ifName string, ifKey db.Key) error { + var err error + if len(ifName) == 0 { + return errors.New("Empty Interface name") + } + app.portTblTs = &db.TableSpec{Name: "PORT_TABLE"} + _, err = dbCl.GetEntry(app.portTblTs, ifKey) + if err != nil { + log.Errorf("Error found on fetching Interface info from App DB for If Name : %s", ifName) + errStr := "Invalid Interface:" + ifName + err = tlerr.InvalidArgsError{Format: errStr} + return err + } + return err +} + +func (app *IntfApp) convertDBIntfInfoToInternal(dbCl *db.DB, ifName string, ifKey db.Key) error { + + var err error + /* Fetching DB data for a specific Interface */ + if len(ifName) > 0 { + log.Info("Updating Interface info from APP-DB to Internal DS for Interface name : ", ifName) + ifInfo, err := dbCl.GetEntry(app.portTblTs, ifKey) + if err != nil { + log.Errorf("Error found on fetching Interface info from App DB for If Name : %s", ifName) + errStr := "Invalid Interface:" + ifName + err = tlerr.InvalidArgsError{Format: errStr} + return err + } + if ifInfo.IsPopulated() { + log.Info("Interface Info populated for ifName : ", ifName) + app.ifTableMap[ifName] = dbEntry{entry: ifInfo} + } else { + return errors.New("Populating Interface info for " + ifName + "failed") + } + } else { + log.Info("App-DB get for all the interfaces") + tbl, err := dbCl.GetTable(app.portTblTs) + if err != nil { + log.Error("App-DB get for list of interfaces failed!") + return err + } + keys, _ := tbl.GetKeys() + for _, key := range keys { + app.convertDBIntfInfoToInternal(dbCl, key.Get(0), db.Key{Comp: []string{key.Get(0)}}) + } + } + return err +} + +/*********** Translation Helper fn to convert DB Interface IP info to Internal DS ***********/ +func (app *IntfApp) convertDBIntfIPInfoToInternal(dbCl *db.DB, ifName string) error { + + var err error + log.Info("Updating Interface IP Info from APP-DB to Internal DS for Interface Name : ", ifName) + app.allIpKeys, _ = app.doGetAllIpKeys(dbCl, app.intfIPTblTs) + + for _, key := range app.allIpKeys { + if len(key.Comp) <= 1 { + continue + } + ipInfo, err := dbCl.GetEntry(app.intfIPTblTs, key) + if err != nil { + log.Errorf("Error found on fetching Interface IP info from App DB for Interface Name : %s", ifName) + return err + } + if len(app.ifIPTableMap[key.Get(0)]) == 0 { + app.ifIPTableMap[key.Get(0)] = make(map[string]dbEntry) + app.ifIPTableMap[key.Get(0)][key.Get(1)] = dbEntry{entry: ipInfo} + } else { + app.ifIPTableMap[key.Get(0)][key.Get(1)] = dbEntry{entry: ipInfo} + } + } + return err +} + +func (app *IntfApp) convertInternalToOCIntfInfo(ifName *string, ifInfo *ocbinds.OpenconfigInterfaces_Interfaces_Interface) { + app.convertInternalToOCIntfAttrInfo(ifName, ifInfo) + app.convertInternalToOCIntfIPAttrInfo(ifName, ifInfo) + app.convertInternalToOCPortStatInfo(ifName, ifInfo) +} + +func (app *IntfApp) convertInternalToOCIntfAttrInfo(ifName *string, ifInfo *ocbinds.OpenconfigInterfaces_Interfaces_Interface) { + + /* Handling the Interface attributes */ + if entry, ok := app.ifTableMap[*ifName]; ok { + ifData := entry.entry + + name := *ifName + ifInfo.Config.Name = &name + ifInfo.State.Name = &name + + for ifAttr := range ifData.Field { + switch ifAttr { + case PORT_ADMIN_STATUS: + adminStatus := ifData.Get(ifAttr) + ifInfo.State.AdminStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus_DOWN + if adminStatus == "up" { + ifInfo.State.AdminStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_AdminStatus_UP + } + case PORT_OPER_STATUS: + operStatus := ifData.Get(ifAttr) + ifInfo.State.OperStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_OperStatus_DOWN + if operStatus == "up" { + ifInfo.State.OperStatus = ocbinds.OpenconfigInterfaces_Interfaces_Interface_State_OperStatus_UP + } + case PORT_DESC: + descVal := ifData.Get(ifAttr) + descr := new(string) + *descr = descVal + ifInfo.Config.Description = descr + ifInfo.State.Description = descr + case PORT_MTU: + mtuStr := ifData.Get(ifAttr) + mtuVal, err := strconv.Atoi(mtuStr) + mtu := new(uint16) + *mtu = uint16(mtuVal) + if err == nil { + ifInfo.Config.Mtu = mtu + ifInfo.State.Mtu = mtu + } + case PORT_SPEED: + speed := ifData.Get(ifAttr) + var speedEnum ocbinds.E_OpenconfigIfEthernet_ETHERNET_SPEED + + switch speed { + case "2500": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_2500MB + case "1000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_1GB + case "5000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_5GB + case "10000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_10GB + case "25000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_25GB + case "40000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_40GB + case "50000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_50GB + case "100000": + speedEnum = ocbinds.OpenconfigIfEthernet_ETHERNET_SPEED_SPEED_100GB + default: + log.Infof("Not supported speed: %s!", speed) + } + ifInfo.Ethernet.Config.PortSpeed = speedEnum + ifInfo.Ethernet.State.PortSpeed = speedEnum + case PORT_INDEX: + ifIdxStr := ifData.Get(ifAttr) + ifIdxNum, err := strconv.Atoi(ifIdxStr) + if err == nil { + ifIdx := new(uint32) + *ifIdx = uint32(ifIdxNum) + ifInfo.State.Ifindex = ifIdx + } + default: + log.Info("Not a valid attribute!") + } + } + } + +} + +func (app *IntfApp) convertInternalToOCIntfIPAttrInfo(ifName *string, ifInfo *ocbinds.OpenconfigInterfaces_Interfaces_Interface) { + + /* Handling the Interface IP attributes */ + subIntf, err := ifInfo.Subinterfaces.NewSubinterface(0) + if err != nil { + log.Error("Creation of subinterface subtree failed!") + return + } + ygot.BuildEmptyTree(subIntf) + if ipMap, ok := app.ifIPTableMap[*ifName]; ok { + for ipKey, _ := range ipMap { + log.Info("IP address = ", ipKey) + ipB, ipNetB, _ := net.ParseCIDR(ipKey) + + v4Flag := false + v6Flag := false + + var v4Address *ocbinds.OpenconfigInterfaces_Interfaces_Interface_Subinterfaces_Subinterface_Ipv4_Addresses_Address + var v6Address *ocbinds.OpenconfigInterfaces_Interfaces_Interface_Subinterfaces_Subinterface_Ipv6_Addresses_Address + + if validIPv4(ipB.String()) { + v4Address, err = subIntf.Ipv4.Addresses.NewAddress(ipB.String()) + v4Flag = true + } else if validIPv6(ipB.String()) { + v6Address, err = subIntf.Ipv6.Addresses.NewAddress(ipB.String()) + v6Flag = true + } else { + log.Error("Invalid IP address " + ipB.String()) + continue + } + + if err != nil { + log.Error("Creation of address subtree failed!") + return + } + if v4Flag { + ygot.BuildEmptyTree(v4Address) + + ipStr := new(string) + *ipStr = ipB.String() + v4Address.Ip = ipStr + v4Address.Config.Ip = ipStr + v4Address.State.Ip = ipStr + + ipNetBNum, _ := ipNetB.Mask.Size() + prfxLen := new(uint8) + *prfxLen = uint8(ipNetBNum) + v4Address.Config.PrefixLength = prfxLen + v4Address.State.PrefixLength = prfxLen + } + if v6Flag { + ygot.BuildEmptyTree(v6Address) + + ipStr := new(string) + *ipStr = ipB.String() + v6Address.Ip = ipStr + v6Address.Config.Ip = ipStr + v6Address.State.Ip = ipStr + + ipNetBNum, _ := ipNetB.Mask.Size() + prfxLen := new(uint8) + *prfxLen = uint8(ipNetBNum) + v6Address.Config.PrefixLength = prfxLen + v6Address.State.PrefixLength = prfxLen + } + } + } +} + +func (app *IntfApp) convertInternalToOCPortStatInfo(ifName *string, ifInfo *ocbinds.OpenconfigInterfaces_Interfaces_Interface) { + if len(app.portStatMap) == 0 { + log.Errorf("Port stat info not present for interface : %s", *ifName) + return + } + if portStatInfo, ok := app.portStatMap[*ifName]; ok { + + inOctet := new(uint64) + inOctetVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_OCTETS"]) + *inOctet = uint64(inOctetVal) + ifInfo.State.Counters.InOctets = inOctet + + inUCastPkt := new(uint64) + inUCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_UCAST_PKTS"]) + *inUCastPkt = uint64(inUCastPktVal) + ifInfo.State.Counters.InUnicastPkts = inUCastPkt + + inNonUCastPkt := new(uint64) + inNonUCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_NON_UCAST_PKTS"]) + *inNonUCastPkt = uint64(inNonUCastPktVal) + + inPkt := new(uint64) + *inPkt = *inUCastPkt + *inNonUCastPkt + ifInfo.State.Counters.InPkts = inPkt + + inBCastPkt := new(uint64) + inBCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_BROADCAST_PKTS"]) + *inBCastPkt = uint64(inBCastPktVal) + ifInfo.State.Counters.InBroadcastPkts = inBCastPkt + + inMCastPkt := new(uint64) + inMCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_MULTICAST_PKTS"]) + *inMCastPkt = uint64(inMCastPktVal) + ifInfo.State.Counters.InMulticastPkts = inMCastPkt + + inErrPkt := new(uint64) + inErrPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_ERRORS"]) + *inErrPkt = uint64(inErrPktVal) + ifInfo.State.Counters.InErrors = inErrPkt + + inDiscPkt := new(uint64) + inDiscPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_IN_DISCARDS"]) + *inDiscPkt = uint64(inDiscPktVal) + ifInfo.State.Counters.InDiscards = inDiscPkt + + outOctet := new(uint64) + outOctetVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_OCTETS"]) + *outOctet = uint64(outOctetVal) + ifInfo.State.Counters.OutOctets = outOctet + + outUCastPkt := new(uint64) + outUCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_UCAST_PKTS"]) + *outUCastPkt = uint64(outUCastPktVal) + ifInfo.State.Counters.OutUnicastPkts = outUCastPkt + + outNonUCastPkt := new(uint64) + outNonUCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_NON_UCAST_PKTS"]) + *outNonUCastPkt = uint64(outNonUCastPktVal) + + outPkt := new(uint64) + *outPkt = *outUCastPkt + *outNonUCastPkt + ifInfo.State.Counters.OutPkts = outPkt + + outBCastPkt := new(uint64) + outBCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_BROADCAST_PKTS"]) + *outBCastPkt = uint64(outBCastPktVal) + ifInfo.State.Counters.OutBroadcastPkts = outBCastPkt + + outMCastPkt := new(uint64) + outMCastPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_MULTICAST_PKTS"]) + *outMCastPkt = uint64(outMCastPktVal) + ifInfo.State.Counters.OutMulticastPkts = outMCastPkt + + outErrPkt := new(uint64) + outErrPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_ERRORS"]) + *outErrPkt = uint64(outErrPktVal) + ifInfo.State.Counters.OutErrors = outErrPkt + + outDiscPkt := new(uint64) + outDiscPktVal, _ := strconv.Atoi(portStatInfo.entry.Field["SAI_PORT_STAT_IF_OUT_DISCARDS"]) + *outDiscPkt = uint64(outDiscPktVal) + ifInfo.State.Counters.OutDiscards = outDiscPkt + } +} + +func (app *IntfApp) translateCommon(d *db.DB, inpOp reqType) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + pathInfo := app.path + + log.Infof("Received UPDATE for path %s; vars=%v", pathInfo.Template, pathInfo.Vars) + + app.allIpKeys, _ = app.doGetAllIpKeys(d, app.intfIPTs) + + intfObj := app.getAppRootObject() + + targetUriPath, err := getYangPathFromUri(app.path.Path) + log.Info("uripath:=", targetUriPath) + log.Info("err:=", err) + + if intfObj.Interface != nil && len(intfObj.Interface) > 0 { + log.Info("len:=", len(intfObj.Interface)) + for ifKey, _ := range intfObj.Interface { + log.Info("Name:=", ifKey) + intf := intfObj.Interface[ifKey] + curr, err := d.GetEntry(app.portTs, db.Key{Comp: []string{ifKey}}) + if err != nil { + errStr := "Invalid Interface:" + ifKey + ifValidErr := tlerr.InvalidArgsError{Format: errStr} + return keys, ifValidErr + } + if !curr.IsPopulated() { + log.Error("Interface ", ifKey, " doesn't exist in DB") + return keys, errors.New("Interface: " + ifKey + " doesn't exist in DB") + } + if intf.Config != nil { + if intf.Config.Description != nil { + log.Info("Description = ", *intf.Config.Description) + curr.Field["description"] = *intf.Config.Description + } else if intf.Config.Mtu != nil { + log.Info("mtu:= ", *intf.Config.Mtu) + curr.Field["mtu"] = strconv.Itoa(int(*intf.Config.Mtu)) + } else if intf.Config.Enabled != nil { + log.Info("enabled = ", *intf.Config.Enabled) + if *intf.Config.Enabled == true { + curr.Field["admin_status"] = "up" + } else { + curr.Field["admin_status"] = "down" + } + } + log.Info("Writing to db for ", ifKey) + var entry dbEntry + entry.op = opUpdate + entry.entry = curr + + app.ifTableMap[ifKey] = entry + } + if intf.Subinterfaces == nil { + continue + } + subIf := intf.Subinterfaces.Subinterface[0] + if subIf != nil { + if subIf.Ipv4 != nil && subIf.Ipv4.Addresses != nil { + for ip, _ := range subIf.Ipv4.Addresses.Address { + addr := subIf.Ipv4.Addresses.Address[ip] + if addr.Config != nil { + log.Info("Ip:=", *addr.Config.Ip) + log.Info("prefix:=", *addr.Config.PrefixLength) + if !validIPv4(*addr.Config.Ip) { + errStr := "Invalid IPv4 address " + *addr.Config.Ip + err = tlerr.InvalidArgsError{Format: errStr} + return keys, err + } + err = app.translateIpv4(d, ifKey, *addr.Config.Ip, int(*addr.Config.PrefixLength)) + if err != nil { + return keys, err + } + } + } + } + if subIf.Ipv6 != nil && subIf.Ipv6.Addresses != nil { + for ip, _ := range subIf.Ipv6.Addresses.Address { + addr := subIf.Ipv6.Addresses.Address[ip] + if addr.Config != nil { + log.Info("Ip:=", *addr.Config.Ip) + log.Info("prefix:=", *addr.Config.PrefixLength) + if !validIPv6(*addr.Config.Ip) { + errStr := "Invalid IPv6 address " + *addr.Config.Ip + err = tlerr.InvalidArgsError{Format: errStr} + return keys, err + } + err = app.translateIpv4(d, ifKey, *addr.Config.Ip, int(*addr.Config.PrefixLength)) + if err != nil { + return keys, err + } + } + } + } + } else { + err = errors.New("Only subinterface index 0 is supported") + return keys, err + } + } + } else { + err = errors.New("Not implemented") + } + + return keys, err +} + +/* Validates whether the IP exists in the DB */ +func (app *IntfApp) validateIp(dbCl *db.DB, ifName string, ip string) error { + app.allIpKeys, _ = app.doGetAllIpKeys(dbCl, app.intfIPTs) + + for _, key := range app.allIpKeys { + if len(key.Comp) < 2 { + continue + } + if key.Get(0) != ifName { + continue + } + ipAddr, _, _ := net.ParseCIDR(key.Get(1)) + ipStr := ipAddr.String() + if ipStr == ip { + log.Infof("IP address %s exists, updating the DS for deletion!", ipStr) + ipInfo, err := dbCl.GetEntry(app.intfIPTs, key) + if err != nil { + log.Error("Error found on fetching Interface IP info from App DB for Interface Name : ", ifName) + return err + } + if len(app.ifIPTableMap[key.Get(0)]) == 0 { + app.ifIPTableMap[key.Get(0)] = make(map[string]dbEntry) + app.ifIPTableMap[key.Get(0)][key.Get(1)] = dbEntry{entry: ipInfo} + } else { + app.ifIPTableMap[key.Get(0)][key.Get(1)] = dbEntry{entry: ipInfo} + } + return nil + } + } + return errors.New(fmt.Sprintf("IP address : %s doesn't exist!", ip)) +} + +func (app *IntfApp) translateIpv4(d *db.DB, intf string, ip string, prefix int) error { + var err error + var ifsKey db.Key + + ifsKey.Comp = []string{intf} + + ipPref := ip + "/" + strconv.Itoa(prefix) + ifsKey.Comp = []string{intf, ipPref} + + log.Info("ifsKey:=", ifsKey) + + log.Info("Checking for IP overlap ....") + ipA, ipNetA, _ := net.ParseCIDR(ipPref) + + for _, key := range app.allIpKeys { + if len(key.Comp) < 2 { + continue + } + ipB, ipNetB, _ := net.ParseCIDR(key.Get(1)) + + if ipNetA.Contains(ipB) || ipNetB.Contains(ipA) { + log.Info("IP ", ipPref, "overlaps with ", key.Get(1), " of ", key.Get(0)) + + if intf != key.Get(0) { + //IP overlap across different interface, reject + log.Error("IP ", ipPref, " overlaps with ", key.Get(1), " of ", key.Get(0)) + + errStr := "IP " + ipPref + " overlaps with IP " + key.Get(1) + " of Interface " + key.Get(0) + err = tlerr.InvalidArgsError{Format: errStr} + return err + } else { + //IP overlap on same interface, replace + var entry dbEntry + entry.op = opDelete + + log.Info("Entry ", key.Get(1), " on ", intf, " needs to be deleted") + if app.ifIPTableMap[intf] == nil { + app.ifIPTableMap[intf] = make(map[string]dbEntry) + } + app.ifIPTableMap[intf][key.Get(1)] = entry + } + } + } + + //At this point, we need to add the entry to db + { + var entry dbEntry + entry.op = opCreate + + m := make(map[string]string) + m["NULL"] = "NULL" + value := db.Value{Field: m} + entry.entry = value + if app.ifIPTableMap[intf] == nil { + app.ifIPTableMap[intf] = make(map[string]dbEntry) + } + app.ifIPTableMap[intf][ipPref] = entry + } + return err +} + +func (app *IntfApp) processCommon(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + + log.Info("processCommon:intf:path =", app.path) + log.Info("ProcessCommon: Target Type is " + reflect.TypeOf(*app.ygotTarget).Elem().Name()) + + for key, entry := range app.ifTableMap { + if entry.op == opUpdate { + log.Info("Updating entry for ", key) + err = d.SetEntry(app.portTs, db.Key{Comp: []string{key}}, entry.entry) + } + } + + for key, entry1 := range app.ifIPTableMap { + ifEntry, err := d.GetEntry(app.intfIPTs, db.Key{Comp: []string{key}}) + if err != nil || !ifEntry.IsPopulated() { + log.Infof("Interface Entry not present for Key:%s for IP config!", key) + m := make(map[string]string) + m["NULL"] = "NULL" + err = d.CreateEntry(app.intfIPTs, db.Key{Comp: []string{key}}, db.Value{Field: m}) + if err != nil { + return resp, err + } + log.Infof("Created Interface entry with Interface name : %s alone!", key) + } + for ip, entry := range entry1 { + if entry.op == opCreate { + log.Info("Creating entry for ", key, ":", ip) + err = d.CreateEntry(app.intfIPTs, db.Key{Comp: []string{key, ip}}, entry.entry) + } else if entry.op == opDelete { + log.Info("Deleting entry for ", key, ":", ip) + err = d.DeleteEntry(app.intfIPTs, db.Key{Comp: []string{key, ip}}) + } + } + } + return resp, err +} diff --git a/translib/lldp_app.go b/translib/lldp_app.go new file mode 100644 index 000000000..29af42cf8 --- /dev/null +++ b/translib/lldp_app.go @@ -0,0 +1,456 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright 2019 Dell, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +////////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "strconv" + "reflect" + "errors" + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/openconfig/ygot/ygot" + log "github.com/golang/glog" + "strings" + "encoding/hex" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" +) + +const ( + LLDP_REMOTE_CAP_ENABLED = "lldp_rem_sys_cap_enabled" + LLDP_REMOTE_SYS_NAME = "lldp_rem_sys_name" + LLDP_REMOTE_PORT_DESC = "lldp_rem_port_desc" + LLDP_REMOTE_CHASS_ID = "lldp_rem_chassis_id" + LLDP_REMOTE_CAP_SUPPORTED = "lldp_rem_sys_cap_supported" + LLDP_REMOTE_PORT_ID_SUBTYPE = "lldp_rem_port_id_subtype" + LLDP_REMOTE_SYS_DESC = "lldp_rem_sys_desc" + LLDP_REMOTE_REM_TIME = "lldp_rem_time_mark" + LLDP_REMOTE_PORT_ID = "lldp_rem_port_id" + LLDP_REMOTE_REM_ID = "lldp_rem_index" + LLDP_REMOTE_CHASS_ID_SUBTYPE = "lldp_rem_chassis_id_subtype" + LLDP_REMOTE_MAN_ADDR = "lldp_rem_man_addr" +) + +type lldpApp struct { + path *PathInfo + ygotRoot *ygot.GoStruct + ygotTarget *interface{} + appDb *db.DB + neighTs *db.TableSpec + lldpTableMap map[string]db.Value + lldpNeighTableMap map[string]map[string]string + lldpCapTableMap map[string]map[string]bool +} + +func init() { + log.Info("Init called for LLDP modules module") + err := register("/openconfig-lldp:lldp", + &appInfo{appType: reflect.TypeOf(lldpApp{}), + ygotRootType: reflect.TypeOf(ocbinds.OpenconfigLldp_Lldp{}), + isNative: false}) + if err != nil { + log.Fatal("Register LLDP app module with App Interface failed with error=", err) + } + + err = addModel(&ModelData{Name: "openconfig-lldp", + Org: "OpenConfig working group", + Ver: "1.0.2"}) + if err != nil { + log.Fatal("Adding model data to appinterface failed with error=", err) + } +} + +func (app *lldpApp) initialize(data appData) { + log.Info("initialize:lldp:path =", data.path) + *app = lldpApp{path: NewPathInfo(data.path), ygotRoot: data.ygotRoot, ygotTarget: data.ygotTarget} + app.neighTs = &db.TableSpec{Name: "LLDP_ENTRY_TABLE"} + app.lldpTableMap = make(map[string]db.Value) + app.lldpNeighTableMap = make(map[string]map[string]string) + app.lldpCapTableMap = make(map[string]map[string]bool) +} + +func (app *lldpApp) getAppRootObject() (*ocbinds.OpenconfigLldp_Lldp) { + deviceObj := (*app.ygotRoot).(*ocbinds.Device) + return deviceObj.Lldp +} + +func (app *lldpApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateCreate:lldp:path =", app.path) + + err = errors.New("Not implemented") + return keys, err +} + +func (app *lldpApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateUpdate:lldp:path =", app.path) + + err = errors.New("Not implemented") + return keys, err +} + +func (app *lldpApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateReplace:lldp:path =", app.path) + + err = errors.New("Not implemented") + return keys, err +} + +func (app *lldpApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { + var err error + var keys []db.WatchKeys + log.Info("translateDelete:lldp:path =", app.path) + + err = errors.New("Not implemented") + return keys, err +} + +func (app *lldpApp) translateGet(dbs [db.MaxDB]*db.DB) error { + var err error + log.Info("translateGet:lldp:path = ", app.path) + + return err +} + +func (app *lldpApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { + pathInfo := NewPathInfo(path) + notifInfo := notificationInfo{dbno: db.ApplDB} + notSupported := tlerr.NotSupportedError{Format: "Subscribe not supported", Path: path} + + if isSubtreeRequest(pathInfo.Template, "/openconfig-lldp:lldp/interfaces") { + if pathInfo.HasSuffix("/neighbors") || + pathInfo.HasSuffix("/config") || + pathInfo.HasSuffix("/state") { + log.Errorf("Subscribe not supported for %s!", pathInfo.Template) + return nil, nil, notSupported + } + ifKey := pathInfo.Var("name") + if len(ifKey) == 0 { + return nil, nil, errors.New("ifKey given is empty!") + } + log.Info("Interface name = ", ifKey) + if pathInfo.HasSuffix("/interface{}") { + notifInfo.table = db.TableSpec{Name: "LLDP_ENTRY_TABLE"} + notifInfo.key = asKey(ifKey) + notifInfo.needCache = true + return ¬ificationOpts{pType: OnChange}, ¬ifInfo, nil + } + } + return nil, nil, notSupported +} + +func (app *lldpApp) processCreate(d *db.DB) (SetResponse, error) { + var err error + + err = errors.New("Not implemented") + var resp SetResponse + + return resp, err +} + +func (app *lldpApp) processUpdate(d *db.DB) (SetResponse, error) { + var err error + + err = errors.New("Not implemented") + var resp SetResponse + + return resp, err +} + +func (app *lldpApp) processReplace(d *db.DB) (SetResponse, error) { + var err error + var resp SetResponse + err = errors.New("Not implemented") + + return resp, err +} + +func (app *lldpApp) processDelete(d *db.DB) (SetResponse, error) { + var err error + err = errors.New("Not implemented") + var resp SetResponse + + return resp, err +} + +func (app *lldpApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { + var err error + var payload []byte + + app.appDb = dbs[db.ApplDB] + lldpIntfObj := app.getAppRootObject() + + targetUriPath, err := getYangPathFromUri(app.path.Path) + log.Info("lldp processGet") + log.Info("targetUriPath: ", targetUriPath) + + if targetUriPath == "/openconfig-lldp:lldp/interfaces" { + log.Info("Requesting interfaces") + app.getLldpInfoFromDB(nil) + ygot.BuildEmptyTree(lldpIntfObj) + ifInfo := lldpIntfObj.Interfaces + ygot.BuildEmptyTree(ifInfo) + for ifname,_ := range app.lldpNeighTableMap { + oneIfInfo, err := ifInfo.NewInterface(ifname) + if err != nil { + log.Info("Creation of subinterface subtree failed!") + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + ygot.BuildEmptyTree(oneIfInfo) + app.getLldpNeighInfoFromInternalMap(&ifname, oneIfInfo) + if *app.ygotTarget == lldpIntfObj.Interfaces { + payload, err = dumpIetfJson(lldpIntfObj, true) + } else { + log.Info("Wrong request!") + } + + } + } else if targetUriPath == "/openconfig-lldp:lldp/interfaces/interface" { + intfObj := lldpIntfObj.Interfaces + ygot.BuildEmptyTree(intfObj) + if intfObj.Interface != nil && len(intfObj.Interface) > 0 { + for ifname, _ := range intfObj.Interface { + log.Info("if-name = ", ifname) + app.getLldpInfoFromDB(&ifname) + ifInfo := intfObj.Interface[ifname] + ygot.BuildEmptyTree(ifInfo) + app.getLldpNeighInfoFromInternalMap(&ifname, ifInfo) + + if *app.ygotTarget == intfObj.Interface[ifname] { + payload, err = dumpIetfJson(intfObj, true) + if err != nil { + log.Info("Creation of subinterface subtree failed!") + return GetResponse{Payload: payload, ErrSrc: AppErr}, err + } + } else { + log.Info("Wrong request!") + } + } + } else { + log.Info("No data") + } + } + + return GetResponse{Payload:payload}, err +} + +/** Helper function to populate JSON response for GET request **/ +func (app *lldpApp) getLldpNeighInfoFromInternalMap(ifName *string, ifInfo *ocbinds.OpenconfigLldp_Lldp_Interfaces_Interface) { + + ngInfo, err := ifInfo.Neighbors.NewNeighbor(*ifName) + if err != nil { + log.Info("Creation of subinterface subtree failed!") + return + } + ygot.BuildEmptyTree(ngInfo) + neighAttrMap:= app.lldpNeighTableMap[*ifName] + for attr, value := range neighAttrMap { + switch attr { + case LLDP_REMOTE_SYS_NAME: + name := new(string) + *name = value + ngInfo.State.SystemName = name + case LLDP_REMOTE_PORT_DESC: + pdescr := new(string) + *pdescr = value + ngInfo.State.PortDescription = pdescr + case LLDP_REMOTE_CHASS_ID: + chId := new (string) + *chId = value + ngInfo.State.ChassisId = chId + case LLDP_REMOTE_PORT_ID_SUBTYPE: + remPortIdTypeVal, err := strconv.Atoi(value) + if err == nil { + ngInfo.State.PortIdType =ocbinds.E_OpenconfigLldp_PortIdType(remPortIdTypeVal) + } + case LLDP_REMOTE_SYS_DESC: + sdesc:= new(string) + *sdesc = value + ngInfo.State.SystemDescription = sdesc + case LLDP_REMOTE_REM_TIME: + /* Ignore Remote System time */ + case LLDP_REMOTE_PORT_ID: + remPortIdPtr := new(string) + *remPortIdPtr = value + ngInfo.State.PortId = remPortIdPtr + case LLDP_REMOTE_REM_ID: + Id := new(string) + *Id = value + ngInfo.State.Id = Id + case LLDP_REMOTE_CHASS_ID_SUBTYPE: + remChassIdTypeVal , err:=strconv.Atoi(value) + if err == nil { + ngInfo.State.ChassisIdType =ocbinds.E_OpenconfigLldp_ChassisIdType(remChassIdTypeVal) + } + case LLDP_REMOTE_MAN_ADDR: + mgmtAdr:= new(string) + *mgmtAdr = value + ngInfo.State.ManagementAddress = mgmtAdr + default: + log.Info("Not a valid attribute!") + } + } + capLst := app.lldpCapTableMap[*ifName] + for capName, enabled := range capLst { + if capName == "Router" { + capInfo, err := ngInfo.Capabilities.NewCapability(6) + if err == nil { + ygot.BuildEmptyTree(capInfo) + capInfo.State.Name = 6 + capInfo.State.Enabled = &enabled + } + } else if capName == "Repeater" { + capInfo, err := ngInfo.Capabilities.NewCapability(5) + if err == nil { + ygot.BuildEmptyTree(capInfo) + capInfo.State.Name = 5 + capInfo.State.Enabled = &enabled + } + } else if capName == "Bridge" { + capInfo, err := ngInfo.Capabilities.NewCapability(3) + if err == nil { + ygot.BuildEmptyTree(capInfo) + capInfo.State.Name = 3 + capInfo.State.Enabled = &enabled + } + } else { + + } + } +} + +/** Helper function to get information from applDB **/ +func (app *lldpApp) getLldpInfoFromDB(ifname *string) { + + lldpTbl, err := app.appDb.GetTable(app.neighTs) + if err != nil { + log.Info("Can't get lldp table") + return + } + + keys, err := lldpTbl.GetKeys() + if err != nil { + log.Info("Can't get lldp keys") + return + } + + + for _, key := range keys { + log.Info("lldp key = ", key.Get(0)) + + lldpEntry, err := app.appDb.GetEntry(app.neighTs, db.Key{Comp: []string{key.Get(0)}}) + if err != nil { + log.Info("can't access neighbor table for key: ", key.Get(0)) + return + } + + if lldpEntry.IsPopulated() { + log.Info("lldp entry populated for key: ", key.Get(0)) + app.lldpTableMap[key.Get(0)] = lldpEntry + } + } + + for _, key := range keys { + if (ifname != nil && key.Get(0) != *ifname) { + continue + } + entryData := app.lldpTableMap[key.Get(0)] + if len(app.lldpNeighTableMap[key.Get(0)]) == 0 { + app.lldpNeighTableMap[key.Get(0)] = make(map[string]string) + } + for lldpAttr := range entryData.Field { + switch lldpAttr { + case LLDP_REMOTE_CAP_ENABLED: + app.getRemoteSysCap(entryData.Get(lldpAttr), key.Get(0), true) + case LLDP_REMOTE_SYS_NAME: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_SYS_NAME] = entryData.Get(lldpAttr) + case LLDP_REMOTE_PORT_DESC: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_PORT_DESC] = entryData.Get(lldpAttr) + case LLDP_REMOTE_CHASS_ID: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_CHASS_ID] = entryData.Get(lldpAttr) + case LLDP_REMOTE_CAP_SUPPORTED: + app.getRemoteSysCap(entryData.Get(lldpAttr), key.Get(0), false) + case LLDP_REMOTE_PORT_ID_SUBTYPE: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_PORT_ID_SUBTYPE] = entryData.Get(lldpAttr) + case LLDP_REMOTE_SYS_DESC: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_SYS_DESC] = entryData.Get(lldpAttr) + case LLDP_REMOTE_REM_TIME: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_REM_TIME] = entryData.Get(lldpAttr) + case LLDP_REMOTE_PORT_ID: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_PORT_ID] = entryData.Get(lldpAttr) + case LLDP_REMOTE_REM_ID: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_REM_ID] = entryData.Get(lldpAttr) + case LLDP_REMOTE_CHASS_ID_SUBTYPE: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_CHASS_ID_SUBTYPE] = entryData.Get(lldpAttr) + case LLDP_REMOTE_MAN_ADDR: + app.lldpNeighTableMap[key.Get(0)][LLDP_REMOTE_MAN_ADDR] = entryData.Get(lldpAttr) + default: + log.Info("Unknown LLDP Attribute") + } + } + } +} + +/** Helper function to get remote system capabilities into a map **/ +func (app *lldpApp) getRemoteSysCap(capb string, ifname string, setCap bool) { + num_str := strings.Split(capb, " ") + byte, _ := hex.DecodeString(num_str[0] + num_str[1]) + sysCap := byte[0] + sysCap |= byte[1] + + log.Info("sysCap: ", sysCap) + + if (sysCap & (128 >> 1)) != 0 { + if app.lldpCapTableMap[ifname] == nil { + app.lldpCapTableMap[ifname] = make(map[string]bool) + app.lldpCapTableMap[ifname]["Repeater"] = false + } + if (setCap) { + log.Info("Repeater ENABLED") + app.lldpCapTableMap[ifname]["Repeater"] = true + } + } + + if (sysCap & (128 >> 2)) != 0 { + if app.lldpCapTableMap[ifname] == nil { + app.lldpCapTableMap[ifname] = make(map[string]bool) + app.lldpCapTableMap[ifname]["Bridge"] = false + } + if (setCap) { + log.Info("Bridge ENABLED") + app.lldpCapTableMap[ifname]["Bridge"] = true + } + } + + if (sysCap & (128 >> 4)) != 0 { + if app.lldpCapTableMap[ifname] == nil { + app.lldpCapTableMap[ifname] = make(map[string]bool) + app.lldpCapTableMap[ifname]["Router"] = false + } + if (setCap) { + log.Info("Router ENABLED") + app.lldpCapTableMap[ifname]["Router"] = true + } + } +} + diff --git a/translib/nonyang_app.go.demo b/translib/nonyang_app.go.demo new file mode 100644 index 000000000..55f003d0c --- /dev/null +++ b/translib/nonyang_app.go.demo @@ -0,0 +1,412 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "encoding/json" + "fmt" + "reflect" + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" + + log "github.com/golang/glog" +) + +// nonYangDemoApp holds all invocation and state information for +// the non-yang demo app +type nonYangDemoApp struct { + // request information + path *PathInfo + reqData []byte + + // DB client to operate on config_db + confDB *db.DB + + // Cahce for read operation + respJSON interface{} +} + +type jsonObject map[string]interface{} +type jsonArray []interface{} + +var ( + vlanTable = &db.TableSpec{Name: "VLAN"} + memberTable = &db.TableSpec{Name: "VLAN_MEMBER"} +) + +func init() { + register( + "/nonyang/", + &appInfo{appType: reflect.TypeOf(nonYangDemoApp{}), + tablesToWatch: []*db.TableSpec{vlanTable, memberTable}, + isNative: true}) +} + +// initialize function prepares this nonYangDemoApp instance +// for a new request handling. +func (app *nonYangDemoApp) initialize(data appData) { + app.path = NewPathInfo(data.path) + app.reqData = data.payload +} + +func (app *nonYangDemoApp) translateCreate(d *db.DB) ([]db.WatchKeys, error) { + app.confDB = d + return nil, nil +} + +func (app *nonYangDemoApp) translateUpdate(d *db.DB) ([]db.WatchKeys, error) { + return nil, tlerr.NotSupported("Unsuppoted operation") +} + +func (app *nonYangDemoApp) translateReplace(d *db.DB) ([]db.WatchKeys, error) { + return nil, tlerr.NotSupported("Unsuppoted operation") +} + +func (app *nonYangDemoApp) translateDelete(d *db.DB) ([]db.WatchKeys, error) { + app.confDB = d + return nil, nil +} + +func (app *nonYangDemoApp) translateGet(dbs [db.MaxDB]*db.DB) error { + return nil +} + +func (app *nonYangDemoApp) translateSubscribe(dbs [db.MaxDB]*db.DB, path string) (*notificationOpts, *notificationInfo, error) { + err := tlerr.NotSupported("Unsuppoted operation") + return nil, nil, err +} + +func (app *nonYangDemoApp) processCreate(d *db.DB) (SetResponse, error) { + var resp SetResponse + pathInfo := app.path + var err error + + log.Infof("Received CREATE for path %s; vars=%v", pathInfo.Template, pathInfo.Vars) + + switch pathInfo.Template { + case "/nonyang/vlan": + err = app.doCreateVlans() + + case "/nonyang/vlan/{}/member": + err = app.doCreateVlanMembers() + + default: + err = tlerr.NotSupported("Unknown path") + } + + return resp, err +} + +func (app *nonYangDemoApp) processUpdate(d *db.DB) (SetResponse, error) { + var resp SetResponse + return resp, tlerr.NotSupported("Unsuppoted operation") +} + +func (app *nonYangDemoApp) processReplace(d *db.DB) (SetResponse, error) { + var resp SetResponse + return resp, tlerr.NotSupported("Unsuppoted operation") +} + +func (app *nonYangDemoApp) processDelete(d *db.DB) (SetResponse, error) { + var resp SetResponse + pathInfo := app.path + var err error + + log.Infof("Received DELETE for path %s; vars=%v", pathInfo.Template, pathInfo.Vars) + + switch pathInfo.Template { + case "/nonyang/vlan/{}": + err = app.doDeleteVlan() + + case "/nonyang/vlan/{}/member/{}": + err = app.doDeleteVlanMember() + + default: + err = tlerr.NotSupported("Unknown path") + } + + return resp, err +} + +func (app *nonYangDemoApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { + app.confDB = dbs[db.ConfigDB] + pathInfo := app.path + var err error + + log.Infof("Received GET for path %s; vars=%v", pathInfo.Template, pathInfo.Vars) + + switch pathInfo.Template { + case "/nonyang/vlan": + err = app.doGetAllVlans() + + case "/nonyang/vlan/{}": + err = app.doGetVlanByID() + + default: + err = tlerr.NotSupported("Unknown path") + } + + var respData []byte + if err == nil && app.respJSON != nil { + respData, err = json.Marshal(app.respJSON) + } + + return GetResponse{Payload: respData}, err +} + +// doGetAllVlans is the handler for "/nonyang/vlan" path +// Loads all vlans and member data from db and prepares +// a json array - each item being one vlan info. +func (app *nonYangDemoApp) doGetAllVlans() error { + log.Infof("in GetAllVlans") + + // Get all vlans from db + t, err := app.confDB.GetTable(vlanTable) + if err != nil { + return err + } + + var allVlansJSON jsonArray + + keys, _ := t.GetKeys() + log.Infof("Found %d VLAN table keys", len(keys)) + for _, key := range keys { + log.Infof("Processing %v", key.Get(0)) + + vlanInfo, _ := t.GetEntry(key) + vlanJSON, err := app.getVlanJSON(&vlanInfo) + if err != nil { + return err + } + + allVlansJSON = append(allVlansJSON, *vlanJSON) + } + + app.respJSON = &allVlansJSON + return nil +} + +// doGetVlanByID is the handler for "/nonyang/vlan/{id}" path. +// Loads data for one vlan and its members and prepares a json +// object. +func (app *nonYangDemoApp) doGetVlanByID() error { + vlanID, _ := app.path.IntVar("id") + log.Infof("in GetVlanByID(), vid=%d", vlanID) + + vlanEntry, err := app.getVlanEntry(vlanID) + if err == nil { + app.respJSON, err = app.getVlanJSON(&vlanEntry) + } + + return err +} + +// getVlanJSON prepares a raw json object for given VLAN table +// entry. Member information are fetched from VLAN_MEMBER table. +func (app *nonYangDemoApp) getVlanJSON(vlanEntry *db.Value) (*jsonObject, error) { + vlanJSON := make(jsonObject) + var memberListJSON jsonArray + + vlanID, _ := vlanEntry.GetInt("vlanid") + vlanName := toVlanName(vlanID) + + log.Infof("Preparing json for vlan %d", vlanID) + + memberPorts := vlanEntry.GetList("members") + log.Infof("%s members = %v", vlanName, memberPorts) + + for _, portName := range memberPorts { + memberJSON := make(jsonObject) + memberJSON["port"] = portName + + memberEntry, err := app.confDB.GetEntry(memberTable, asKey(vlanName, portName)) + if isNotFoundError(err) { + log.Infof("Failed to load VLAN_MEMBER %s,%s; err=%v", vlanName, portName, err) + } else if err != nil { + return nil, err + } else { + memberJSON["mode"] = memberEntry.Get("tagging_mode") + } + + memberListJSON = append(memberListJSON, memberJSON) + } + + vlanJSON["id"] = vlanID + vlanJSON["name"] = vlanName + vlanJSON["members"] = memberListJSON + + return &vlanJSON, nil +} + +// doCreateVlans handles CREATE operation on "/nonyang/vlan" path. +func (app *nonYangDemoApp) doCreateVlans() error { + log.Infof("in doCreateVlans()") + + // vlan creation expects array of vlan ids. + var vlansJSON []int + err := json.Unmarshal(app.reqData, &vlansJSON) + if err != nil { + log.Errorf("Failed to parse input.. err=%v", err) + return tlerr.InvalidArgs("Invalid input") + } + + log.Infof("Receieved %d vlan ids; %v", len(vlansJSON), vlansJSON) + + for _, vid := range vlansJSON { + vlanName := toVlanName(vid) + log.Infof("NEW vlan entry '%s'", vlanName) + + entry := db.Value{Field: make(map[string]string)} + entry.SetInt("vlanid", vid) + err = app.confDB.CreateEntry(vlanTable, asKey(vlanName), entry) + if err != nil { + return err + } + } + + return nil +} + +// doCreateVlanMembers handles CREATE operation on path +// "/nonyang/vlan/{}/member" +func (app *nonYangDemoApp) doCreateVlanMembers() error { + vlanID, _ := app.path.IntVar("id") + log.Infof("in doCreateVlanMembers(), vid=%d", vlanID) + + var memberListJSON []map[string]string + err := json.Unmarshal(app.reqData, &memberListJSON) + if err != nil { + log.Errorf("Failed to parse input.. err=%v", err) + return tlerr.InvalidArgs("Invalid input") + } + + vlanName := toVlanName(vlanID) + vlanEntry, err := app.getVlanEntry(vlanID) + if err != nil { + return err + } + + membersList := vlanEntry.GetList("members") + + for _, memberJSON := range memberListJSON { + log.Infof("Processing member info %v", memberJSON) + + portName, _ := memberJSON["port"] + membersList = append(membersList, portName) + + taggingMode, ok := memberJSON["mode"] + if !ok { + taggingMode = "tagged" + } + + log.Infof("NEW vlan_member entry '%s|%s'; mode=%s", vlanName, portName, taggingMode) + memberEntry := db.Value{Field: make(map[string]string)} + memberEntry.Set("tagging_mode", taggingMode) + err = app.confDB.CreateEntry(memberTable, asKey(vlanName, portName), memberEntry) + if err != nil { + return err + } + } + + // Update the vlan table with new member list + log.Infof("SET vlan entry '%s', members=%v", vlanName, membersList) + vlanEntry.SetList("members", membersList) + err = app.confDB.ModEntry(vlanTable, asKey(vlanName), vlanEntry) + + return err +} + +func (app *nonYangDemoApp) doDeleteVlan() error { + vlanID, _ := app.path.IntVar("id") + log.Infof("in doDeleteVlan(); vid=%d", vlanID) + + vlanName := toVlanName(vlanID) + vlanKey := asKey(vlanName) + vlanEntry, err := app.confDB.GetEntry(vlanTable, vlanKey) + if isNotFoundError(err) { + log.Infof("Vlan %d not found.. nothing to delete", vlanID) + return nil + } else if err != nil { + return err + } + + // Delete VLAN_MEMBER table entry for each member port + for _, portName := range vlanEntry.GetList("members") { + log.Infof("DEL vlan_member entry '%s|%s'", vlanName, portName) + err = app.confDB.DeleteEntry(memberTable, asKey(vlanName, portName)) + if err != nil { + return err + } + } + + // Delete VLAN table entry + log.Infof("DEL vlan entry '%s'", vlanName) + err = app.confDB.DeleteEntry(vlanTable, vlanKey) + + return err +} + +func (app *nonYangDemoApp) doDeleteVlanMember() error { + vlanID, _ := app.path.IntVar("id") + portName := app.path.Var("port") + log.Infof("in doDeleteVlanMember(); vid=%d, member=%s", vlanID, portName) + + vlanName := toVlanName(vlanID) + vlanEntry, err := app.getVlanEntry(vlanID) + if err != nil { + return err + } + + membersList := vlanEntry.GetList("members") + updatedList := removeElement(membersList, portName) + + // Ignore the request if the port is not a member + if len(membersList) == len(updatedList) { + log.Infof("Vlan %d has no member %s", vlanID, portName) + return nil + } + + // Update VLAN entry with new member list + log.Infof("SET vlan entry '%s', members=%v", vlanName, updatedList) + vlanEntry.SetList("members", updatedList) + err = app.confDB.SetEntry(vlanTable, asKey(vlanName), vlanEntry) + if err != nil { + return err + } + + // Delete VLAN_MEMBER entry + log.Infof("DEL vlan_member entry '%s|%s'", vlanName, portName) + err = app.confDB.DeleteEntry(memberTable, asKey(vlanName, portName)) + + return err +} + +func (app *nonYangDemoApp) getVlanEntry(vlanID int) (db.Value, error) { + entry, err := app.confDB.GetEntry(vlanTable, asKey(toVlanName(vlanID))) + if isNotFoundError(err) { + err = tlerr.NotFound("VLAN %v does not exists", vlanID) + } + return entry, err +} + +// toVlanName returns the vlan name for given vlan id. +func toVlanName(vid int) string { + return fmt.Sprintf("Vlan%d", vid) +} diff --git a/translib/ocbinds/oc.go b/translib/ocbinds/oc.go new file mode 100644 index 000000000..d4fb61b4a --- /dev/null +++ b/translib/ocbinds/oc.go @@ -0,0 +1,22 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package ocbinds + +//go:generate sh -c "$GO run -mod=vendor ../../vendor/github.com/openconfig/ygot/generator/generator.go -generate_fakeroot -output_file ocbinds.go -package_name ocbinds -generate_fakeroot -fakeroot_name=device -compress_paths=false -exclude_modules ietf-interfaces -path . $(find ../../models/yang -name '*.yang' -not -name '*annot.yang')" diff --git a/translib/path_utils.go b/translib/path_utils.go new file mode 100644 index 000000000..1083f1054 --- /dev/null +++ b/translib/path_utils.go @@ -0,0 +1,191 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + + log "github.com/golang/glog" + "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/ygot" + "github.com/openconfig/ygot/ytypes" +) + +// PathInfo structure contains parsed path information. +type PathInfo struct { + Path string + Template string + Vars map[string]string +} + +// Var returns the string value for a path variable. Returns +// empty string if no such variable exists. +func (p *PathInfo) Var(name string) string { + return p.Vars[name] +} + +// IntVar returns the value for a path variable as an int. +// Returns 0 if no such variable exists. Returns an error +// if the value is not an integer. +func (p *PathInfo) IntVar(name string) (int, error) { + val := p.Vars[name] + if len(val) == 0 { + return 0, nil + } + + return strconv.Atoi(val) +} + +// HasPrefix checks if this path template starts with given +// prefix.. Shorthand for strings.HasPrefix(p.Template, s) +func (p *PathInfo) HasPrefix(s string) bool { + return strings.HasPrefix(p.Template, s) +} + +// HasSuffix checks if this path template ends with given +// suffix.. Shorthand for strings.HasSuffix(p.Template, s) +func (p *PathInfo) HasSuffix(s string) bool { + return strings.HasSuffix(p.Template, s) +} + +// NewPathInfo parses given path string into a PathInfo structure. +func NewPathInfo(path string) *PathInfo { + var info PathInfo + info.Path = path + info.Vars = make(map[string]string) + + //TODO optimize using regexp + var template strings.Builder + r := strings.NewReader(path) + + for r.Len() > 0 { + c, _ := r.ReadByte() + if c != '[' { + template.WriteByte(c) + continue + } + + name := readUntil(r, '=') + value := readUntil(r, ']') + if len(name) != 0 { + fmt.Fprintf(&template, "{}") + info.Vars[name] = value + } + } + + info.Template = template.String() + + return &info +} + +func readUntil(r *strings.Reader, delim byte) string { + var buff strings.Builder + for { + c, err := r.ReadByte() + if err == nil && c != delim { + buff.WriteByte(c) + } else { + break + } + } + + return buff.String() +} + +func getParentNode(targetUri *string, deviceObj *ocbinds.Device) (*interface{}, *yang.Entry, error) { + path, err := ygot.StringToPath(*targetUri, ygot.StructuredPath, ygot.StringSlicePath) + if err != nil { + return nil, nil, err + } + + var pathList []*gnmi.PathElem = path.Elem + + parentPath := &gnmi.Path{} + + for i := 0; i < (len(pathList) - 1); i++ { + pathSlice := strings.Split(pathList[i].Name, ":") + pathList[i].Name = pathSlice[len(pathSlice)-1] + parentPath.Elem = append(parentPath.Elem, pathList[i]) + } + + treeNodeList, err2 := ytypes.GetNode(ygSchema.RootSchema(), deviceObj, parentPath) + if err2 != nil { + return nil, nil, err2 + } + + if len(treeNodeList) == 0 { + return nil, nil, errors.New("Invalid URI") + } + + return &(treeNodeList[0].Data), treeNodeList[0].Schema, nil +} + +func getNodeName(targetUri *string, deviceObj *ocbinds.Device) (string, error) { + path, err := ygot.StringToPath(*targetUri, ygot.StructuredPath, ygot.StringSlicePath) + if err != nil { + log.Error("Error in uri to path conversion: ", err) + return "", err + } + + pathList := path.Elem + for i := 0; i < len(pathList); i++ { + pathSlice := strings.Split(pathList[i].Name, ":") + pathList[i].Name = pathSlice[len(pathSlice)-1] + } + + treeNodeList, err := ytypes.GetNode(ygSchema.RootSchema(), deviceObj, path) + if err != nil { + log.Error("Error in uri to path conversion: ", err) + return "", err + } + + if len(treeNodeList) == 0 { + return "", errors.New("Invalid URI") + } + + return treeNodeList[0].Schema.Name, nil +} + +func getObjectFieldName(targetUri *string, deviceObj *ocbinds.Device, ygotTarget *interface{}) (string, error) { + parentObjIntf, _, err := getParentNode(targetUri, deviceObj) + if err != nil { + return "", err + } + valObj := reflect.ValueOf(*parentObjIntf).Elem() + parentObjType := reflect.TypeOf(*parentObjIntf).Elem() + + for i := 0; i < parentObjType.NumField(); i++ { + if reflect.ValueOf(*ygotTarget).Kind() == reflect.Ptr && valObj.Field(i).Kind() == reflect.Ptr { + if valObj.Field(i).Pointer() == reflect.ValueOf(*ygotTarget).Pointer() { + return parentObjType.Field(i).Name, nil + } + } else if valObj.Field(i).String() == reflect.ValueOf(*ygotTarget).String() { + return parentObjType.Field(i).Name, nil + } + } + return "", errors.New("Target object not found") +} diff --git a/translib/path_utils_test.go b/translib/path_utils_test.go new file mode 100644 index 000000000..3b5b292b6 --- /dev/null +++ b/translib/path_utils_test.go @@ -0,0 +1,164 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package translib + +import ( + "reflect" + "strings" + "testing" + + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" +) + +func TestGetParentNode(t *testing.T) { + + tests := []struct { + tid int + targetUri string + appRootType reflect.Type + want string + }{{ + tid: 1, + targetUri: "/openconfig-acl:acl/acl-sets/", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "OpenconfigAcl_Acl", + }, { + tid: 2, + targetUri: "/openconfig-acl:acl/acl-sets/openconfig-acl:acl-set[name=MyACL1][type=ACL_IPV4]/", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "OpenconfigAcl_Acl_AclSets", + }, { + tid: 3, + targetUri: "/openconfig-acl:acl/acl-sets/openconfig-acl:acl-set[name=Sample][type=ACL_IPV4]/state/description", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "OpenconfigAcl_Acl_AclSets_AclSet_State", + }} + + for _, tt := range tests { + var deviceObj ocbinds.Device = ocbinds.Device{} + _, err := getRequestBinder(&tt.targetUri, nil, 1, &tt.appRootType).unMarshallUri(&deviceObj) + if err != nil { + t.Error("TestGetParentNode: Error in unmarshalling the URI", err) + } else { + parentNode, _, err := getParentNode(&tt.targetUri, &deviceObj) + if err != nil { + t.Error("TestGetParentNode: Error in getting the parent node: ", err) + } else if parentNode == nil { + t.Error("TestGetParentNode: Error in getting the parent node") + } else if reflect.TypeOf(*parentNode).Elem().Name() != tt.want { + t.Error("TestGetParentNode: Error in getting the parent node: ", reflect.TypeOf(*parentNode).Elem().Name()) + } + } + } +} + +func TestGetNodeName(t *testing.T) { + + tests := []struct { + tid int + targetUri string + appRootType reflect.Type + want string + }{{ + tid: 1, + targetUri: "/openconfig-acl:acl/acl-sets/", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "acl-sets", + }, { + tid: 2, + targetUri: "/openconfig-acl:acl/acl-sets/openconfig-acl:acl-set[name=MyACL1][type=ACL_IPV4]/", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "acl-set", + }, { + tid: 3, + targetUri: "/openconfig-acl:acl/acl-sets/openconfig-acl:acl-set[name=Sample][type=ACL_IPV4]/state/description", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "description", + }, { + tid: 4, // Negative test case + targetUri: "/openconfig-acl:acl/acl-sets/acl-set[name=Sample][type=ACL_IPV4]/state/descriptXX", + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "rpc error: code = InvalidArgument desc = no match found in *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_State, for path elem:", + }, { + //Negative test case + tid: 9, + uri: "/openconfig-acl:acl/acl-sets/openconfig-acl:acl-set[name=Sample][type=ACL_IPV4]/state/descriptXX", + opcode: 1, + payload: []byte{}, + appRootType: reflect.TypeOf(ocbinds.OpenconfigAcl_Acl{}), + want: "rpc error: code = InvalidArgument desc = no match found in *ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_State, for path elem: 0 { + for _, err := range errs { + fmt.Fprintln(os.Stderr, err) + } + } +} + +func getOcModelsList () ([]string) { + var fileList []string + file, err := os.Open(YangPath + "models_list") + if err != nil { + return fileList + } + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + fileEntry := scanner.Text() + if strings.HasPrefix(fileEntry, "#") != true { + _, err := os.Stat(YangPath + fileEntry) + if err != nil { + continue + } + fileList = append(fileList, fileEntry) + } + } + return fileList +} + +func getDefaultModelsList () ([]string) { + var files []string + fileInfo, err := ioutil.ReadDir(YangPath) + if err != nil { + return files + } + + for _, file := range fileInfo { + if strings.HasPrefix(file.Name(), "sonic-") && !strings.HasSuffix(file.Name(), "-dev.yang") && filepath.Ext(file.Name()) == ".yang" { + files = append(files, file.Name()) + } + } + return files +} + +func init() { + yangFiles := []string{} + ocList := getOcModelsList() + yangFiles = getDefaultModelsList() + yangFiles = append(yangFiles, ocList...) + fmt.Println("Yang model List:", yangFiles) + err := loadYangModules(yangFiles...) + if err != nil { + fmt.Fprintln(os.Stderr, err) + } +} + +func loadYangModules(files ...string) error { + + var err error + + paths := []string{YangPath} + + for _, path := range paths { + expanded, err := yang.PathsWithModules(path) + if err != nil { + fmt.Fprintln(os.Stderr, err) + continue + } + yang.AddPath(expanded...) + } + + ms := yang.NewModules() + + for _, name := range files { + if err := ms.Read(name); err != nil { + fmt.Fprintln(os.Stderr, err) + continue + } + } + + // Process the Yang files + reportIfError(ms.Process()) + + // Keep track of the top level modules we read in. + // Those are the only modules we want to print below. + mods := map[string]*yang.Module{} + var names []string + + for _, m := range ms.Modules { + if mods[m.Name] == nil { + mods[m.Name] = m + names = append(names, m.Name) + } + } + + sonic_entries := make([]*yang.Entry, len(names)) + oc_entries := make(map[string]*yang.Entry) + oc_annot_entries := make([]*yang.Entry, len(names)) + sonic_annot_entries := make([]*yang.Entry, len(names)) + + for _, n := range names { + if strings.Contains(n, "annot") && strings.Contains(n, "sonic") { + sonic_annot_entries = append(sonic_annot_entries, yang.ToEntry(mods[n])) + } else if strings.Contains(n, "annot") { + oc_annot_entries = append(oc_annot_entries, yang.ToEntry(mods[n])) + } else if strings.Contains(n, "sonic") { + sonic_entries = append(sonic_entries, yang.ToEntry(mods[n])) + } else if oc_entries[n] == nil { + oc_entries[n] = yang.ToEntry(mods[n]) + } + } + + dbMapBuild(sonic_entries) + annotDbSpecMap(sonic_annot_entries) + annotToDbMapBuild(oc_annot_entries) + yangToDbMapBuild(oc_entries) + + return err +} diff --git a/translib/transformer/xconst.go b/translib/transformer/xconst.go new file mode 100644 index 000000000..8fcdabdc7 --- /dev/null +++ b/translib/transformer/xconst.go @@ -0,0 +1,51 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +const ( + YANG_MODULE = "module" + YANG_LIST = "list" + YANG_CONTAINER = "container" + YANG_LEAF = "leaf" + YANG_LEAF_LIST = "leaf-list" + + YANG_ANNOT_DB_NAME = "db-name" + YANG_ANNOT_TABLE_NAME = "table-name" + YANG_ANNOT_FIELD_NAME = "field-name" + YANG_ANNOT_KEY_DELIM = "key-delimiter" + YANG_ANNOT_TABLE_XFMR = "table-transformer" + YANG_ANNOT_FIELD_XFMR = "field-transformer" + YANG_ANNOT_KEY_XFMR = "key-transformer" + YANG_ANNOT_POST_XFMR = "post-transformer" + YANG_ANNOT_SUBTREE_XFMR = "subtree-transformer" + YANG_ANNOT_VALIDATE_FUNC = "get-validate" + + REDIS_DB_TYPE_APPLN = "APPL_DB" + REDIS_DB_TYPE_ASIC = "ASIC_DB" + REDIS_DB_TYPE_CONFIG = "CONFIG_DB" + REDIS_DB_TYPE_COUNTER = "COUNTERS_DB" + REDIS_DB_TYPE_LOG_LVL = "LOGLEVEL_DB" + REDIS_DB_TYPE_STATE = "STATE_DB" + REDIS_DB_TYPE_FLX_COUNTER = "FLEX_COUNTER_DB" + + XPATH_SEP_FWD_SLASH = "/" + XFMR_EMPTY_STRING = "" + SONIC_TABLE_INDEX = 2 + +) diff --git a/translib/transformer/xfmr_acl.go b/translib/transformer/xfmr_acl.go new file mode 100644 index 000000000..7db975153 --- /dev/null +++ b/translib/transformer/xfmr_acl.go @@ -0,0 +1,976 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "bytes" + "errors" + "fmt" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" + "reflect" + "strconv" + "strings" + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" +) + +func init() { + XlateFuncBind("DbToYang_acl_set_name_xfmr", DbToYang_acl_set_name_xfmr) + XlateFuncBind("YangToDb_acl_type_field_xfmr", YangToDb_acl_type_field_xfmr) + XlateFuncBind("DbToYang_acl_type_field_xfmr", DbToYang_acl_type_field_xfmr) + XlateFuncBind("YangToDb_acl_set_key_xfmr", YangToDb_acl_set_key_xfmr) + XlateFuncBind("DbToYang_acl_set_key_xfmr", DbToYang_acl_set_key_xfmr) + XlateFuncBind("YangToDb_acl_entry_key_xfmr", YangToDb_acl_entry_key_xfmr) + XlateFuncBind("DbToYang_acl_entry_key_xfmr", DbToYang_acl_entry_key_xfmr) + XlateFuncBind("DbToYang_acl_entry_sequenceid_xfmr", DbToYang_acl_entry_sequenceid_xfmr) + XlateFuncBind("YangToDb_acl_l2_ethertype_xfmr", YangToDb_acl_l2_ethertype_xfmr) + XlateFuncBind("DbToYang_acl_l2_ethertype_xfmr", DbToYang_acl_l2_ethertype_xfmr) + XlateFuncBind("YangToDb_acl_ip_protocol_xfmr", YangToDb_acl_ip_protocol_xfmr) + XlateFuncBind("DbToYang_acl_ip_protocol_xfmr", DbToYang_acl_ip_protocol_xfmr) + XlateFuncBind("YangToDb_acl_source_port_xfmr", YangToDb_acl_source_port_xfmr) + XlateFuncBind("DbToYang_acl_source_port_xfmr", DbToYang_acl_source_port_xfmr) + XlateFuncBind("YangToDb_acl_destination_port_xfmr", YangToDb_acl_destination_port_xfmr) + XlateFuncBind("DbToYang_acl_destination_port_xfmr", DbToYang_acl_destination_port_xfmr) + XlateFuncBind("YangToDb_acl_tcp_flags_xfmr", YangToDb_acl_tcp_flags_xfmr) + XlateFuncBind("DbToYang_acl_tcp_flags_xfmr", DbToYang_acl_tcp_flags_xfmr) + XlateFuncBind("YangToDb_acl_port_bindings_xfmr", YangToDb_acl_port_bindings_xfmr) + XlateFuncBind("DbToYang_acl_port_bindings_xfmr", DbToYang_acl_port_bindings_xfmr) + XlateFuncBind("YangToDb_acl_forwarding_action_xfmr", YangToDb_acl_forwarding_action_xfmr) + XlateFuncBind("DbToYang_acl_forwarding_action_xfmr", DbToYang_acl_forwarding_action_xfmr) + XlateFuncBind("validate_ipv4", validate_ipv4) + XlateFuncBind("validate_ipv6", validate_ipv6) + XlateFuncBind("acl_post_xfmr", acl_post_xfmr) +} + +const ( + ACL_TABLE = "ACL_TABLE" + RULE_TABLE = "ACL_RULE" + SONIC_ACL_TYPE_IPV4 = "L3" + SONIC_ACL_TYPE_L2 = "L2" + SONIC_ACL_TYPE_IPV6 = "L3V6" + OPENCONFIG_ACL_TYPE_IPV4 = "ACL_IPV4" + OPENCONFIG_ACL_TYPE_IPV6 = "ACL_IPV6" + OPENCONFIG_ACL_TYPE_L2 = "ACL_L2" + ACL_TYPE = "type" + MIN_PRIORITY = 1 + MAX_PRIORITY = 65535 +) + +/* E_OpenconfigAcl_ACL_TYPE */ +var ACL_TYPE_MAP = map[string]string{ + strconv.FormatInt(int64(ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4), 10): SONIC_ACL_TYPE_IPV4, + strconv.FormatInt(int64(ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6), 10): SONIC_ACL_TYPE_IPV6, + strconv.FormatInt(int64(ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2), 10): SONIC_ACL_TYPE_L2, +} + +/* E_OpenconfigAcl_FORWARDING_ACTION */ +var ACL_FORWARDING_ACTION_MAP = map[string]string{ + strconv.FormatInt(int64(ocbinds.OpenconfigAcl_FORWARDING_ACTION_ACCEPT), 10): "FORWARD", + strconv.FormatInt(int64(ocbinds.OpenconfigAcl_FORWARDING_ACTION_DROP), 10): "DROP", + strconv.FormatInt(int64(ocbinds.OpenconfigAcl_FORWARDING_ACTION_REJECT), 10): "REDIRECT", +} + +/* E_OpenconfigPacketMatchTypes_IP_PROTOCOL */ +var IP_PROTOCOL_MAP = map[string]string{ + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_ICMP), 10): "1", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_IGMP), 10): "2", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_TCP), 10): "6", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_UDP), 10): "17", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_RSVP), 10): "46", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_GRE), 10): "47", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_AUTH), 10): "51", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_PIM), 10): "103", + strconv.FormatInt(int64(ocbinds.OpenconfigPacketMatchTypes_IP_PROTOCOL_IP_L2TP), 10): "115", +} + +var ETHERTYPE_MAP = map[ocbinds.E_OpenconfigPacketMatchTypes_ETHERTYPE]uint32{ + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_LLDP: 0x88CC, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_VLAN: 0x8100, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_ROCE: 0x8915, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_ARP: 0x0806, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV4: 0x0800, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV6: 0x86DD, + ocbinds.OpenconfigPacketMatchTypes_ETHERTYPE_ETHERTYPE_MPLS: 0x8847, +} + +func getAclRoot(s *ygot.GoStruct) *ocbinds.OpenconfigAcl_Acl { + deviceObj := (*s).(*ocbinds.Device) + return deviceObj.Acl +} + +func getAclTypeOCEnumFromName(val string) (ocbinds.E_OpenconfigAcl_ACL_TYPE, error) { + switch val { + case "ACL_IPV4", "openconfig-acl:ACL_IPV4": + return ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4, nil + case "ACL_IPV6", "openconfig-acl:ACL_IPV6": + return ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6, nil + case "ACL_L2", "openconfig-acl:ACL_L2": + return ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2, nil + default: + return ocbinds.OpenconfigAcl_ACL_TYPE_UNSET, + tlerr.NotSupported("ACL Type '%s' not supported", val) + } +} +func getAclKeyStrFromOCKey(aclname string, acltype ocbinds.E_OpenconfigAcl_ACL_TYPE) string { + aclN := strings.Replace(strings.Replace(aclname, " ", "_", -1), "-", "_", -1) + aclT := acltype.ΛMap()["E_OpenconfigAcl_ACL_TYPE"][int64(acltype)].Name + return aclN + "_" + aclT +} + +func getOCAclKeysFromStrDBKey(aclKey string) (string, ocbinds.E_OpenconfigAcl_ACL_TYPE) { + var aclOrigName string + var aclOrigType ocbinds.E_OpenconfigAcl_ACL_TYPE + + if strings.Contains(aclKey, "_"+OPENCONFIG_ACL_TYPE_IPV4) { + aclOrigName = strings.Replace(aclKey, "_"+OPENCONFIG_ACL_TYPE_IPV4, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 + } else if strings.Contains(aclKey, "_"+OPENCONFIG_ACL_TYPE_IPV6) { + aclOrigName = strings.Replace(aclKey, "_"+OPENCONFIG_ACL_TYPE_IPV6, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 + } else if strings.Contains(aclKey, "_"+OPENCONFIG_ACL_TYPE_L2) { + aclOrigName = strings.Replace(aclKey, "_"+OPENCONFIG_ACL_TYPE_L2, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 + } + + return aclOrigName, aclOrigType +} + +func getTransportConfigTcpFlags(tcpFlags string) []ocbinds.E_OpenconfigPacketMatchTypes_TCP_FLAGS { + var flags []ocbinds.E_OpenconfigPacketMatchTypes_TCP_FLAGS + if len(tcpFlags) > 0 { + flagStr := strings.Split(tcpFlags, "/")[0] + flagNumber, _ := strconv.ParseUint(strings.Replace(flagStr, "0x", "", -1), 16, 32) + for i := 0; i < 8; i++ { + mask := 1 << uint(i) + if (int(flagNumber) & mask) > 0 { + switch int(flagNumber) & mask { + case 0x01: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_FIN) + case 0x02: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_SYN) + case 0x04: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_RST) + case 0x08: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_PSH) + case 0x10: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ACK) + case 0x20: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_URG) + case 0x40: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ECE) + case 0x80: + flags = append(flags, ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_CWR) + default: + } + } + } + } + return flags +} + +func getL2EtherType(etherType uint64) interface{} { + for k, v := range ETHERTYPE_MAP { + if uint32(etherType) == v { + return k + } + } + return uint16(etherType) +} + +//////////////////////////////////////////// +// Validate callpoints +//////////////////////////////////////////// +var validate_ipv4 ValidateCallpoint = func(inParams XfmrParams) (bool) { + if strings.Contains(inParams.key, "ACL_IPV4") { + return true + } + return false +} +var validate_ipv6 ValidateCallpoint = func(inParams XfmrParams) (bool) { + if strings.Contains(inParams.key, "ACL_IPV6") { + return true + } + return false +} + +//////////////////////////////////////////// +// Post Transformer +//////////////////////////////////////////// +var acl_post_xfmr PostXfmrFunc = func(inParams XfmrParams) (map[string]map[string]db.Value, error) { + log.Info("In Post transformer") + //TODO: check if a default ACL Rule exists, else create one and update the resultMap with default rule + // Return will be the updated result map + return (*inParams.dbDataMap)[inParams.curDb], nil +} + +//////////////////////////////////////////// +// Bi-directoonal overloaded methods +//////////////////////////////////////////// +var YangToDb_acl_forwarding_action_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + if inParams.param == nil { + res_map["PACKET_ACTION"] = "" + return res_map, err + } + action, _ := inParams.param.(ocbinds.E_OpenconfigAcl_FORWARDING_ACTION) + log.Info("YangToDb_acl_forwarding_action_xfmr: ", inParams.ygRoot, " Xpath: ", inParams.uri, " forwarding_action: ", action) + res_map["PACKET_ACTION"] = findInMap(ACL_FORWARDING_ACTION_MAP, strconv.FormatInt(int64(action), 10)) + return res_map, err +} +var DbToYang_acl_forwarding_action_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_forwarding_action_xfmr", data, inParams.ygRoot) + oc_action := findInMap(ACL_FORWARDING_ACTION_MAP, data[RULE_TABLE][inParams.key].Field["PACKET_ACTION"]) + n, err := strconv.ParseInt(oc_action, 10, 64) + result["forwarding-action"] = ocbinds.E_OpenconfigAcl_FORWARDING_ACTION(n).ΛMap()["E_OpenconfigAcl_FORWARDING_ACTION"][n].Name + return result, err +} + +var YangToDb_acl_type_field_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + if inParams.param == nil { + res_map[ACL_TYPE] = "" + return res_map, err + } + + acltype, _ := inParams.param.(ocbinds.E_OpenconfigAcl_ACL_TYPE) + log.Info("YangToDb_acl_type_field_xfmr: ", inParams.ygRoot, " Xpath: ", inParams.uri, " acltype: ", acltype) + res_map[ACL_TYPE] = findInMap(ACL_TYPE_MAP, strconv.FormatInt(int64(acltype), 10)) + return res_map, err +} +var DbToYang_acl_type_field_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_type_field_xfmr", data, inParams.ygRoot) + oc_acltype := findInMap(ACL_TYPE_MAP, data[ACL_TABLE][inParams.key].Field[ACL_TYPE]) + n, err := strconv.ParseInt(oc_acltype, 10, 64) + result[ACL_TYPE] = ocbinds.E_OpenconfigAcl_ACL_TYPE(n).ΛMap()["E_OpenconfigAcl_ACL_TYPE"][n].Name + return result, err +} + +var YangToDb_acl_set_key_xfmr KeyXfmrYangToDb = func(inParams XfmrParams) (string, error) { + var aclkey string + var err error + var oc_aclType ocbinds.E_OpenconfigAcl_ACL_TYPE + log.Info("YangToDb_acl_set_key_xfmr: ", inParams.ygRoot, inParams.uri) + pathInfo := NewPathInfo(inParams.uri) + + if len(pathInfo.Vars) < 2 { + err = errors.New("Invalid xpath, key attributes not found") + return aclkey, err + } + + oc_aclType, err = getAclTypeOCEnumFromName(pathInfo.Var("type")) + if err != nil { + err = errors.New("OC Acl type name to OC Acl Enum failed") + return aclkey, err + } + + aclkey = getAclKeyStrFromOCKey(pathInfo.Var("name"), oc_aclType) + log.Info("YangToDb_acl_set_key_xfmr - acl_set_key : ", aclkey) + + return aclkey, err +} + +var DbToYang_acl_set_key_xfmr KeyXfmrDbToYang = func(inParams XfmrParams) (map[string]interface{}, error) { + rmap := make(map[string]interface{}) + var err error + var aclNameStr string + var aclTypeStr string + aclkey := inParams.key + log.Info("DbToYang_acl_set_key_xfmr: ", aclkey) + if strings.Contains(aclkey, "_"+OPENCONFIG_ACL_TYPE_IPV4) { + aclNameStr = strings.Replace(aclkey, "_"+OPENCONFIG_ACL_TYPE_IPV4, "", 1) + aclTypeStr = "ACL_IPV4" + } else if strings.Contains(aclkey, "_"+OPENCONFIG_ACL_TYPE_IPV6) { + aclNameStr = strings.Replace(aclkey, "_"+OPENCONFIG_ACL_TYPE_IPV6, "", 1) + aclTypeStr = "ACL_IPV6" + } else if strings.Contains(aclkey, "_"+OPENCONFIG_ACL_TYPE_L2) { + aclNameStr = strings.Replace(aclkey, "_"+OPENCONFIG_ACL_TYPE_L2, "", 1) + aclTypeStr = "ACL_L2" + } else { + err = errors.New("Invalid key for acl set.") + log.Info("Invalid Keys for acl acl set", aclkey) + } + rmap["name"] = aclNameStr + rmap["type"] = aclTypeStr + return rmap, err +} + +var DbToYang_acl_set_name_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + res_map := make(map[string]interface{}) + var err error + log.Info("DbToYang_acl_set_name_xfmr: ", inParams.key) + /*name attribute corresponds to key in redis table*/ + aclName, _ := getOCAclKeysFromStrDBKey(inParams.key) + res_map["name"] = aclName + log.Info("acl-set/config/name ", res_map) + return res_map, err +} + +var YangToDb_acl_entry_key_xfmr KeyXfmrYangToDb = func(inParams XfmrParams) (string, error) { + var entry_key string + var err error + var oc_aclType ocbinds.E_OpenconfigAcl_ACL_TYPE + log.Info("YangToDb_acl_entry_key_xfmr: ", inParams.ygRoot, inParams.uri) + pathInfo := NewPathInfo(inParams.uri) + + if len(pathInfo.Vars) < 3 { + err = errors.New("Invalid xpath, key attributes not found") + return entry_key, err + } + + oc_aclType, err = getAclTypeOCEnumFromName(pathInfo.Var("type")) + if err != nil { + err = errors.New("OC Acl type name to OC Acl Enum failed") + return entry_key, err + } + + aclkey := getAclKeyStrFromOCKey(pathInfo.Var("name"), oc_aclType) + var rulekey string + if strings.Contains(pathInfo.Template, "/acl-entry{sequence-id}") { + rulekey = "RULE_" + pathInfo.Var("sequence-id") + } + entry_key = aclkey + "|" + rulekey + + log.Info("YangToDb_acl_entry_key_xfmr - entry_key : ", entry_key) + + return entry_key, err +} + +var DbToYang_acl_entry_key_xfmr KeyXfmrDbToYang = func(inParams XfmrParams) (map[string]interface{}, error) { + rmap := make(map[string]interface{}) + var err error + entry_key := inParams.key + log.Info("DbToYang_acl_entry_key_xfmr: ", entry_key) + + key := strings.Split(entry_key, "|") + if len(key) < 2 { + err = errors.New("Invalid key for acl entries.") + log.Info("Invalid Keys for acl enmtries", entry_key) + return rmap, err + } + + dbAclRule := key[1] + seqId := strings.Replace(dbAclRule, "RULE_", "", 1) + rmap["sequence-id"], _ = strconv.ParseFloat(seqId, 64) + return rmap, err +} + +var DbToYang_acl_entry_sequenceid_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + res_map := make(map[string]interface{}) + var err error + log.Info("DbToYang_acl_entry_sequenceid_xfmr: ", inParams.key) + /*sequenec-id attribute corresponds to key in redis table*/ + res, err := DbToYang_acl_entry_key_xfmr(inParams) + log.Info("acl-entry/config/sequence-id ", res) + if err != nil { + return res_map, err + } + if seqId, ok := res["sequence-id"]; !ok { + log.Error("sequence-id not found in acl entry") + return res_map, err + } else { + res_map["sequence-id"] = seqId + } + return res_map, err +} + +var YangToDb_acl_l2_ethertype_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + + if inParams.param == nil { + res_map["ETHER_TYPE"] = "" + return res_map, err + } + ethertypeType := reflect.TypeOf(inParams.param).Elem() + log.Info("YangToDb_acl_ip_protocol_xfmr: ", inParams.ygRoot, " Xpath: ", inParams.uri, " ethertypeType: ", ethertypeType) + var b bytes.Buffer + switch ethertypeType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_E_OpenconfigPacketMatchTypes_ETHERTYPE{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_E_OpenconfigPacketMatchTypes_ETHERTYPE) + fmt.Fprintf(&b, "0x%0.4x", ETHERTYPE_MAP[v.E_OpenconfigPacketMatchTypes_ETHERTYPE]) + res_map["ETHER_TYPE"] = b.String() + break + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_Uint16{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_L2_Config_Ethertype_Union_Uint16) + fmt.Fprintf(&b, "0x%0.4x", v.Uint16) + res_map["ETHER_TYPE"] = b.String() + break + } + return res_map, err +} + +var DbToYang_acl_l2_ethertype_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_l2_ethertype_xfmr", data, inParams.ygRoot) + if _, ok := data[RULE_TABLE]; !ok { + err = errors.New("RULE_TABLE entry not found in the input param") + return result, err + } + + ruleTbl := data[RULE_TABLE] + ruleInst := ruleTbl[inParams.key] + etype, ok := ruleInst.Field["ETHER_TYPE"] + + if ok { + etypeVal, _ := strconv.ParseUint(strings.Replace(etype, "0x", "", -1), 16, 32) + result["protocol"] = getL2EtherType(etypeVal) + } else { + err = errors.New("ETHER_TYPE field not found in DB") + } + return result, nil +} + +var YangToDb_acl_ip_protocol_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + + if inParams.param == nil { + res_map["IP_PROTOCOL"] = "" + return res_map, err + } + protocolType := reflect.TypeOf(inParams.param).Elem() + log.Info("YangToDb_acl_ip_protocol_xfmr: ", inParams.ygRoot, " Xpath: ", inParams.uri, " protocolType: ", protocolType) + switch protocolType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_E_OpenconfigPacketMatchTypes_IP_PROTOCOL{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_E_OpenconfigPacketMatchTypes_IP_PROTOCOL) + res_map["IP_PROTOCOL"] = findInMap(IP_PROTOCOL_MAP, strconv.FormatInt(int64(v.E_OpenconfigPacketMatchTypes_IP_PROTOCOL), 10)) + v = nil + break + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_Uint8{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Ipv4_Config_Protocol_Union_Uint8) + res_map["IP_PROTOCOL"] = strconv.FormatInt(int64(v.Uint8), 10) + break + } + return res_map, err +} + +var DbToYang_acl_ip_protocol_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_ip_protocol_xfmr", data, inParams.ygRoot) + oc_protocol := findByValue(IP_PROTOCOL_MAP, data[RULE_TABLE][inParams.key].Field["IP_PROTOCOL"]) + n, err := strconv.ParseInt(oc_protocol, 10, 64) + result["protocol"] = ocbinds.E_OpenconfigPacketMatchTypes_IP_PROTOCOL(n).ΛMap()["E_OpenconfigPacketMatchTypes_IP_PROTOCOL"][n].Name + return result, err +} + +var YangToDb_acl_source_port_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + if inParams.param == nil { + res_map["L4_SRC_PORT"] = "" + return res_map, err + } + sourceportType := reflect.TypeOf(inParams.param).Elem() + log.Info("YangToDb_acl_ip_protocol_xfmr: ", inParams.ygRoot, " Xpath: ", inParams.uri, " sourceportType: ", sourceportType) + switch sourceportType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort) + res_map["L4_SRC_PORT"] = v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort.ΛMap()["E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort"][int64(v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort)].Name + break + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_String{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_String) + res_map["L4_SRC_PORT_RANGE"] = strings.Replace(v.String, "..", "-", 1) + break + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_Uint16{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_SourcePort_Union_Uint16) + res_map["L4_SRC_PORT"] = strconv.FormatInt(int64(v.Uint16), 10) + break + } + return res_map, err +} + +var DbToYang_acl_source_port_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_source_port_xfmr: ", data, inParams.ygRoot) + result := make(map[string]interface{}) + if _, ok := data[RULE_TABLE]; !ok { + err = errors.New("RULE_TABLE entry not found in the input param") + return result, err + } + ruleTbl := data[RULE_TABLE] + ruleInst := ruleTbl[inParams.key] + port, ok := ruleInst.Field["L4_SRC_PORT"] + if ok { + result["source-port"] = port + return result, nil + } + + portRange, ok := ruleInst.Field["L4_SRC_PORT_RANGE"] + if ok { + result["source-port"] = portRange + return result, nil + } else { + err = errors.New("PORT/PORT_RANGE field not found in DB") + } + return result, err +} + +var YangToDb_acl_destination_port_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + if inParams.param == nil { + res_map["L4_DST_PORT_RANGE"] = "" + return res_map, err + } + destportType := reflect.TypeOf(inParams.param).Elem() + log.Info("YangToDb_acl_ip_protocol_xfmr: ", inParams.ygRoot, " Xpath: ", inParams.uri, " destportType: ", destportType) + switch destportType { + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort) + res_map["L4_DST_PORT"] = v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort.ΛMap()["E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort"][int64(v.E_OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort)].Name + break + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_String{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_String) + res_map["L4_DST_PORT_RANGE"] = strings.Replace(v.String, "..", "-", 1) + break + case reflect.TypeOf(ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_Uint16{}): + v := (inParams.param).(*ocbinds.OpenconfigAcl_Acl_AclSets_AclSet_AclEntries_AclEntry_Transport_Config_DestinationPort_Union_Uint16) + res_map["L4_DST_PORT"] = strconv.FormatInt(int64(v.Uint16), 10) + break + } + return res_map, err +} + +var DbToYang_acl_destination_port_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_destination_port_xfmr: ", data, inParams.ygRoot) + if _, ok := data[RULE_TABLE]; !ok { + err = errors.New("RULE_TABLE entry not found in the input param") + return result, err + } + ruleTbl := data[RULE_TABLE] + ruleInst := ruleTbl[inParams.key] + port, ok := ruleInst.Field["L4_DST_PORT"] + if ok { + result["destination-port"] = port + return result, nil + } + + portRange, ok := ruleInst.Field["L4_DST_PORT_RANGE"] + if ok { + result["destination-port"] = portRange + return result, nil + } else { + err = errors.New("DST PORT/PORT_RANGE field not found in DB") + } + return result, err +} + +var YangToDb_acl_tcp_flags_xfmr FieldXfmrYangToDb = func(inParams XfmrParams) (map[string]string, error) { + res_map := make(map[string]string) + var err error + log.Info("YangToDb_acl_tcp_flags_xfmr: ") + var tcpFlags uint32 = 0x00 + var b bytes.Buffer + if inParams.param == nil { + res_map["TCP_FLAGS"] = b.String() + return res_map, err + } + log.Info("YangToDb_acl_tcp_flags_xfmr: ", inParams.ygRoot, inParams.uri) + v := reflect.ValueOf(inParams.param) + + flags := v.Interface().([]ocbinds.E_OpenconfigPacketMatchTypes_TCP_FLAGS) + for _, flag := range flags { + fmt.Println("TCP Flag name: " + flag.ΛMap()["E_OpenconfigPacketMatchTypes_TCP_FLAGS"][int64(flag)].Name) + switch flag { + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_FIN: + tcpFlags |= 0x01 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_SYN: + tcpFlags |= 0x02 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_RST: + tcpFlags |= 0x04 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_PSH: + tcpFlags |= 0x08 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ACK: + tcpFlags |= 0x10 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_URG: + tcpFlags |= 0x20 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_ECE: + tcpFlags |= 0x40 + break + case ocbinds.OpenconfigPacketMatchTypes_TCP_FLAGS_TCP_CWR: + tcpFlags |= 0x80 + break + } + } + fmt.Fprintf(&b, "0x%0.2x/0x%0.2x", tcpFlags, tcpFlags) + res_map["TCP_FLAGS"] = b.String() + return res_map, err +} + +var DbToYang_acl_tcp_flags_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + var err error + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_tcp_flags_xfmr: ", data, inParams.ygRoot) + result := make(map[string]interface{}) + if _, ok := data[RULE_TABLE]; !ok { + err = errors.New("RULE_TABLE entry not found in the input param") + return result, err + } + ruleTbl := data[RULE_TABLE] + ruleInst := ruleTbl[inParams.key] + tcpFlag, ok := ruleInst.Field["TCP_FLAGS"] + if ok { + result["tcp-flags"] = getTransportConfigTcpFlags(tcpFlag) + return result, nil + } + return result, nil +} + +var YangToDb_acl_port_bindings_xfmr SubTreeXfmrYangToDb = func(inParams XfmrParams) (map[string]map[string]db.Value, error) { + var err error + res_map := make(map[string]map[string]db.Value) + aclTableMap := make(map[string]db.Value) + log.Info("YangToDb_acl_port_bindings_xfmr: ", inParams.ygRoot, inParams.uri) + + aclObj := getAclRoot(inParams.ygRoot) + if aclObj.Interfaces == nil { + return res_map, err + } + aclInterfacesMap := make(map[string][]string) + for intfId, _ := range aclObj.Interfaces.Interface { + intf := aclObj.Interfaces.Interface[intfId] + if intf != nil { + if intf.IngressAclSets != nil && len(intf.IngressAclSets.IngressAclSet) > 0 { + for inAclKey, _ := range intf.IngressAclSets.IngressAclSet { + aclName := getAclKeyStrFromOCKey(inAclKey.SetName, inAclKey.Type) + aclInterfacesMap[aclName] = append(aclInterfacesMap[aclName], *intf.Id) + _, ok := aclTableMap[aclName] + if !ok { + aclTableMap[aclName] = db.Value{Field: make(map[string]string)} + } + aclTableMap[aclName].Field["stage"] = "INGRESS" + } + } + if intf.EgressAclSets != nil && len(intf.EgressAclSets.EgressAclSet) > 0 { + for outAclKey, _ := range intf.EgressAclSets.EgressAclSet { + aclName := getAclKeyStrFromOCKey(outAclKey.SetName, outAclKey.Type) + aclInterfacesMap[aclName] = append(aclInterfacesMap[aclName], *intf.Id) + _, ok := aclTableMap[aclName] + if !ok { + aclTableMap[aclName] = db.Value{Field: make(map[string]string)} + } + aclTableMap[aclName].Field["stage"] = "EGRESS" + } + } + } + } + for k, _ := range aclInterfacesMap { + val := aclTableMap[k] + (&val).SetList("ports", aclInterfacesMap[k]) + } + res_map[ACL_TABLE] = aclTableMap + return res_map, err +} + +var DbToYang_acl_port_bindings_xfmr SubTreeXfmrDbToYang = func(inParams XfmrParams) error { + var err error + data := (*inParams.dbDataMap)[inParams.curDb] + log.Info("DbToYang_acl_port_bindings_xfmr: ", data, inParams.ygRoot) + + aclTbl := data["ACL_TABLE"] + var ruleTbl map[string]map[string]db.Value + + // repopulate to use existing code + ruleTbl = make(map[string]map[string]db.Value) + for key, element := range data["ACL_RULE"] { + // split into aclKey and ruleKey + tokens := strings.Split(key, "|") + if ruleTbl[tokens[0]] == nil { + ruleTbl[tokens[0]] = make(map[string]db.Value) + } + ruleTbl[tokens[0]][tokens[1]] = db.Value{Field: make(map[string]string)} + ruleTbl[tokens[0]][tokens[1]] = element + } + + pathInfo := NewPathInfo(inParams.uri) + + acl := getAclRoot(inParams.ygRoot) + targetUriPath, _ := getYangPathFromUri(pathInfo.Path) + if isSubtreeRequest(pathInfo.Template, "/openconfig-acl:acl/interfaces/interface{}") { + for intfId := range acl.Interfaces.Interface { + intfData := acl.Interfaces.Interface[intfId] + ygot.BuildEmptyTree(intfData) + if isSubtreeRequest(targetUriPath, "/openconfig-acl:acl/interfaces/interface/ingress-acl-sets") { + err = getAclBindingInfoForInterfaceData(aclTbl, ruleTbl, intfData, intfId, "INGRESS") + } else if isSubtreeRequest(targetUriPath, "/openconfig-acl:acl/interfaces/interface/egress-acl-sets") { + err = getAclBindingInfoForInterfaceData(aclTbl, ruleTbl, intfData, intfId, "EGRESS") + } else { + err = getAclBindingInfoForInterfaceData(aclTbl, ruleTbl, intfData, intfId, "INGRESS") + if err != nil { + return err + } + err = getAclBindingInfoForInterfaceData(aclTbl, ruleTbl, intfData, intfId, "EGRESS") + } + } + } else { + err = getAllBindingsInfo(aclTbl, ruleTbl, inParams.ygRoot) + } + + return err +} + +func convertInternalToOCAclRuleBinding(aclTableMap map[string]db.Value, ruleTableMap map[string]map[string]db.Value, priority uint32, seqId int64, direction string, aclSet ygot.GoStruct, entrySet ygot.GoStruct) { + if seqId == -1 { + seqId = int64(MAX_PRIORITY - priority) + } + + var num uint64 + num = 0 + var ruleId uint32 = uint32(seqId) + + if direction == "INGRESS" { + var ingressEntrySet *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_AclEntries_AclEntry + var ok bool + if entrySet == nil { + ingressAclSet := aclSet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet) + if ingressEntrySet, ok = ingressAclSet.AclEntries.AclEntry[ruleId]; !ok { + ingressEntrySet, _ = ingressAclSet.AclEntries.NewAclEntry(ruleId) + } + } else { + ingressEntrySet = entrySet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_AclEntries_AclEntry) + } + if ingressEntrySet != nil { + ygot.BuildEmptyTree(ingressEntrySet) + ingressEntrySet.State.SequenceId = &ruleId + ingressEntrySet.State.MatchedPackets = &num + ingressEntrySet.State.MatchedOctets = &num + } + } else if direction == "EGRESS" { + var egressEntrySet *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_AclEntries_AclEntry + var ok bool + if entrySet == nil { + egressAclSet := aclSet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet) + if egressEntrySet, ok = egressAclSet.AclEntries.AclEntry[ruleId]; !ok { + egressEntrySet, _ = egressAclSet.AclEntries.NewAclEntry(ruleId) + } + } else { + egressEntrySet = entrySet.(*ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_AclEntries_AclEntry) + } + if egressEntrySet != nil { + ygot.BuildEmptyTree(egressEntrySet) + egressEntrySet.State.SequenceId = &ruleId + egressEntrySet.State.MatchedPackets = &num + egressEntrySet.State.MatchedOctets = &num + } + } +} + +func convertInternalToOCAclBinding(aclTableMap map[string]db.Value, ruleTableMap map[string]map[string]db.Value, aclName string, intfId string, direction string, intfAclSet ygot.GoStruct) error { + var err error + if _, ok := aclTableMap[aclName]; !ok { + err = errors.New("Acl entry not found, convertInternalToOCAclBinding") + return err + } else { + aclEntry := aclTableMap[aclName] + if !contains(aclEntry.GetList("ports"), intfId) { + return tlerr.InvalidArgs("Acl %s not binded with %s", aclName, intfId) + } + } + + for ruleName := range ruleTableMap[aclName] { + if ruleName != "DEFAULT_RULE" { + seqId, _ := strconv.Atoi(strings.Replace(ruleName, "RULE_", "", 1)) + convertInternalToOCAclRuleBinding(aclTableMap, ruleTableMap, 0, int64(seqId), direction, intfAclSet, nil) + } + } + + return err +} + +func getAllBindingsInfo(aclTableMap map[string]db.Value, ruleTableMap map[string]map[string]db.Value, ygRoot *ygot.GoStruct) error { + var err error + acl := getAclRoot(ygRoot) + + var interfaces []string + for aclName := range aclTableMap { + aclData := aclTableMap[aclName] + if len(aclData.Get("ports@")) > 0 { + aclIntfs := aclData.GetList("ports") + for i, _ := range aclIntfs { + if !contains(interfaces, aclIntfs[i]) && aclIntfs[i] != "" { + interfaces = append(interfaces, aclIntfs[i]) + } + } + } + } + ygot.BuildEmptyTree(acl) + for _, intfId := range interfaces { + var intfData *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface + intfData, ok := acl.Interfaces.Interface[intfId] + if !ok { + intfData, _ = acl.Interfaces.NewInterface(intfId) + } + ygot.BuildEmptyTree(intfData) + err = getAclBindingInfoForInterfaceData(aclTableMap, ruleTableMap, intfData, intfId, "INGRESS") + err = getAclBindingInfoForInterfaceData(aclTableMap, ruleTableMap, intfData, intfId, "EGRESS") + } + return err +} + +func getAclBindingInfoForInterfaceData(aclTableMap map[string]db.Value, ruleTableMap map[string]map[string]db.Value, intfData *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface, intfId string, direction string) error { + var err error + if intfData != nil { + intfData.Config.Id = intfData.Id + intfData.State.Id = intfData.Id + } + if direction == "INGRESS" { + if intfData.IngressAclSets != nil && len(intfData.IngressAclSets.IngressAclSet) > 0 { + for ingressAclSetKey, _ := range intfData.IngressAclSets.IngressAclSet { + aclName := strings.Replace(strings.Replace(ingressAclSetKey.SetName, " ", "_", -1), "-", "_", -1) + aclType := ingressAclSetKey.Type.ΛMap()["E_OpenconfigAcl_ACL_TYPE"][int64(ingressAclSetKey.Type)].Name + aclKey := aclName + "_" + aclType + + ingressAclSet := intfData.IngressAclSets.IngressAclSet[ingressAclSetKey] + if ingressAclSet != nil && ingressAclSet.AclEntries != nil && len(ingressAclSet.AclEntries.AclEntry) > 0 { + for seqId, _ := range ingressAclSet.AclEntries.AclEntry { + rulekey := "RULE_" + strconv.Itoa(int(seqId)) + entrySet := ingressAclSet.AclEntries.AclEntry[seqId] + _, ok := ruleTableMap[aclKey+"|"+rulekey] + if !ok { + log.Info("Acl Rule not found ", aclKey, rulekey) + err = errors.New("Acl Rule not found ingress, getAclBindingInfoForInterfaceData") + return err + } + convertInternalToOCAclRuleBinding(aclTableMap, ruleTableMap, 0, int64(seqId), direction, nil, entrySet) + } + } else { + ygot.BuildEmptyTree(ingressAclSet) + ingressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_Config{SetName: &aclName, Type: ingressAclSetKey.Type} + ingressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_State{SetName: &aclName, Type: ingressAclSetKey.Type} + err = convertInternalToOCAclBinding(aclTableMap, ruleTableMap, aclKey, intfId, direction, ingressAclSet) + } + } + } else { + err = findAndGetAclBindingInfoForInterfaceData(aclTableMap, ruleTableMap, intfId, direction, intfData) + } + } else if direction == "EGRESS" { + if intfData.EgressAclSets != nil && len(intfData.EgressAclSets.EgressAclSet) > 0 { + for egressAclSetKey, _ := range intfData.EgressAclSets.EgressAclSet { + aclName := strings.Replace(strings.Replace(egressAclSetKey.SetName, " ", "_", -1), "-", "_", -1) + aclType := egressAclSetKey.Type.ΛMap()["E_OpenconfigAcl_ACL_TYPE"][int64(egressAclSetKey.Type)].Name + aclKey := aclName + "_" + aclType + + egressAclSet := intfData.EgressAclSets.EgressAclSet[egressAclSetKey] + if egressAclSet != nil && egressAclSet.AclEntries != nil && len(egressAclSet.AclEntries.AclEntry) > 0 { + for seqId, _ := range egressAclSet.AclEntries.AclEntry { + rulekey := "RULE_" + strconv.Itoa(int(seqId)) + entrySet := egressAclSet.AclEntries.AclEntry[seqId] + _, ok := ruleTableMap[aclKey+"|"+rulekey] + if !ok { + log.Info("Acl Rule not found ", aclKey, rulekey) + err = errors.New("Acl Rule not found egress, getAclBindingInfoForInterfaceData") + return err + } + convertInternalToOCAclRuleBinding(aclTableMap, ruleTableMap, 0, int64(seqId), direction, nil, entrySet) + } + } else { + ygot.BuildEmptyTree(egressAclSet) + egressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_Config{SetName: &aclName, Type: egressAclSetKey.Type} + egressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_State{SetName: &aclName, Type: egressAclSetKey.Type} + err = convertInternalToOCAclBinding(aclTableMap, ruleTableMap, aclKey, intfId, direction, egressAclSet) + } + } + } else { + err = findAndGetAclBindingInfoForInterfaceData(aclTableMap, ruleTableMap, intfId, direction, intfData) + } + } else { + log.Error("Unknown direction") + } + return err +} + +func findAndGetAclBindingInfoForInterfaceData(aclTableMap map[string]db.Value, ruleTableMap map[string]map[string]db.Value, intfId string, direction string, intfData *ocbinds.OpenconfigAcl_Acl_Interfaces_Interface) error { + var err error + for aclName, _ := range aclTableMap { + aclData := aclTableMap[aclName] + aclIntfs := aclData.GetList("ports") + aclType := aclData.Get(ACL_TYPE) + var aclOrigName string + var aclOrigType ocbinds.E_OpenconfigAcl_ACL_TYPE + if SONIC_ACL_TYPE_IPV4 == aclType { + aclOrigName = strings.Replace(aclName, "_"+OPENCONFIG_ACL_TYPE_IPV4, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV4 + } else if SONIC_ACL_TYPE_IPV6 == aclType { + aclOrigName = strings.Replace(aclName, "_"+OPENCONFIG_ACL_TYPE_IPV6, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_IPV6 + } else if SONIC_ACL_TYPE_L2 == aclType { + aclOrigName = strings.Replace(aclName, "_"+OPENCONFIG_ACL_TYPE_L2, "", 1) + aclOrigType = ocbinds.OpenconfigAcl_ACL_TYPE_ACL_L2 + } + + if contains(aclIntfs, intfId) && direction == aclData.Get("stage") { + if direction == "INGRESS" { + if intfData.IngressAclSets != nil { + aclSetKey := ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_Key{SetName: aclOrigName, Type: aclOrigType} + ingressAclSet, ok := intfData.IngressAclSets.IngressAclSet[aclSetKey] + if !ok { + ingressAclSet, _ = intfData.IngressAclSets.NewIngressAclSet(aclOrigName, aclOrigType) + ygot.BuildEmptyTree(ingressAclSet) + ingressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_Config{SetName: &aclOrigName, Type: aclOrigType} + ingressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_IngressAclSets_IngressAclSet_State{SetName: &aclOrigName, Type: aclOrigType} + } + err = convertInternalToOCAclBinding(aclTableMap, ruleTableMap, aclName, intfId, direction, ingressAclSet) + if err != nil { + return err + } + } + } else if direction == "EGRESS" { + if intfData.EgressAclSets != nil { + aclSetKey := ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_Key{SetName: aclOrigName, Type: aclOrigType} + egressAclSet, ok := intfData.EgressAclSets.EgressAclSet[aclSetKey] + if !ok { + egressAclSet, _ = intfData.EgressAclSets.NewEgressAclSet(aclOrigName, aclOrigType) + ygot.BuildEmptyTree(egressAclSet) + egressAclSet.Config = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_Config{SetName: &aclOrigName, Type: aclOrigType} + egressAclSet.State = &ocbinds.OpenconfigAcl_Acl_Interfaces_Interface_EgressAclSets_EgressAclSet_State{SetName: &aclOrigName, Type: aclOrigType} + } + err = convertInternalToOCAclBinding(aclTableMap, ruleTableMap, aclName, intfId, direction, egressAclSet) + if err != nil { + return err + } + } + } + } + } + return err +} diff --git a/translib/transformer/xfmr_interface.go b/translib/transformer/xfmr_interface.go new file mode 100644 index 000000000..57bc650c8 --- /dev/null +++ b/translib/transformer/xfmr_interface.go @@ -0,0 +1,135 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "github.com/openconfig/ygot/ygot" + "github.com/Azure/sonic-mgmt-common/translib/db" + log "github.com/golang/glog" +) + +type XfmrParams struct { + d *db.DB + dbs [db.MaxDB]*db.DB + curDb db.DBNum + ygRoot *ygot.GoStruct + uri string + oper int + key string + dbDataMap *map[db.DBNum]map[string]map[string]db.Value + param interface{} +} + +/** + * KeyXfmrYangToDb type is defined to use for conversion of Yang key to DB Key + * Transformer function definition. + * Param: XfmrParams structure having Database info, YgotRoot, operation, Xpath + * Return: Database keys to access db entry, error + **/ +type KeyXfmrYangToDb func (inParams XfmrParams) (string, error) +/** + * KeyXfmrDbToYang type is defined to use for conversion of DB key to Yang key + * Transformer function definition. + * Param: XfmrParams structure having Database info, operation, Database keys to access db entry + * Return: multi dimensional map to hold the yang key attributes of complete xpath, error + **/ +type KeyXfmrDbToYang func (inParams XfmrParams) (map[string]interface{}, error) + +/** + * FieldXfmrYangToDb type is defined to use for conversion of yang Field to DB field + * Transformer function definition. + * Param: Database info, YgotRoot, operation, Xpath + * Return: multi dimensional map to hold the DB data, error + **/ +type FieldXfmrYangToDb func (inParams XfmrParams) (map[string]string, error) +/** + * FieldXfmrDbtoYang type is defined to use for conversion of DB field to Yang field + * Transformer function definition. + * Param: XfmrParams structure having Database info, operation, DB data in multidimensional map, output param YgotRoot + * Return: error + **/ +type FieldXfmrDbtoYang func (inParams XfmrParams) (map[string]interface{}, error) + +/** + * SubTreeXfmrYangToDb type is defined to use for handling the yang subtree to DB + * Transformer function definition. + * Param: XfmrParams structure having Database info, YgotRoot, operation, Xpath + * Return: multi dimensional map to hold the DB data, error + **/ +type SubTreeXfmrYangToDb func (inParams XfmrParams) (map[string]map[string]db.Value, error) +/** + * SubTreeXfmrDbToYang type is defined to use for handling the DB to Yang subtree + * Transformer function definition. + * Param : XfmrParams structure having Database pointers, current db, operation, DB data in multidimensional map, output param YgotRoot, uri + * Return : error + **/ +type SubTreeXfmrDbToYang func (inParams XfmrParams) (error) +/** + * ValidateCallpoint is used to validate a YANG node during data translation back to YANG as a response to GET + * Param : XfmrParams structure having Database pointers, current db, operation, DB data in multidimensional map, output param YgotRoot, uri + * Return : bool + **/ +type ValidateCallpoint func (inParams XfmrParams) (bool) + +/** + * PostXfmrFunc type is defined to use for handling any default handling operations required as part of the CREATE + * Transformer function definition. + * Param: XfmrParams structure having database pointers, current db, operation, DB data in multidimensional map, YgotRoot, uri + * Return: multi dimensional map to hold the DB data, error + **/ +type PostXfmrFunc func (inParams XfmrParams) (map[string]map[string]db.Value, error) + + +/** + * TableXfmrFunc type is defined to use for table transformer function for dynamic derviation of redis table. + * Param: XfmrParams structure having database pointers, current db, operation, DB data in multidimensional map, YgotRoot, uri + * Return: List of table names, error + **/ +type TableXfmrFunc func (inParams XfmrParams) ([]string, error) + + +/** + * Xfmr validation interface for validating the callback registration of app modules + * transformer methods. + **/ +type XfmrInterface interface { + xfmrInterfaceValiidate() +} + +func (KeyXfmrYangToDb) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for KeyXfmrYangToDb") +} +func (KeyXfmrDbToYang) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for KeyXfmrDbToYang") +} +func (FieldXfmrYangToDb) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for FieldXfmrYangToDb") +} +func (FieldXfmrDbtoYang) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for FieldXfmrDbtoYang") +} +func (SubTreeXfmrYangToDb) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for SubTreeXfmrYangToDb") +} +func (SubTreeXfmrDbToYang) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for SubTreeXfmrDbToYang") +} +func (TableXfmrFunc) xfmrInterfaceValiidate () { + log.Info("xfmrInterfaceValiidate for TableXfmrFunc") +} diff --git a/translib/transformer/xfmr_path_utils.go b/translib/transformer/xfmr_path_utils.go new file mode 100644 index 000000000..8a052cd24 --- /dev/null +++ b/translib/transformer/xfmr_path_utils.go @@ -0,0 +1,99 @@ +/////////////////////////////////////////////////////////////////////// +// +// Copyright 2019 Broadcom. All rights reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +/////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "bytes" + "fmt" + "strings" +) + +// PathInfo structure contains parsed path information. +type PathInfo struct { + Path string + Template string + Vars map[string]string +} + +// Var returns the string value for a path variable. Returns +// empty string if no such variable exists. +func (p *PathInfo) Var(name string) string { + return p.Vars[name] +} + +// NewPathInfo parses given path string into a PathInfo structure. +func NewPathInfo(path string) *PathInfo { + var info PathInfo + info.Path = path + info.Vars = make(map[string]string) + + //TODO optimize using regexp + var template strings.Builder + r := strings.NewReader(path) + + for r.Len() > 0 { + c, _ := r.ReadByte() + if c != '[' { + template.WriteByte(c) + continue + } + + name := readUntil(r, '=') + value := readUntil(r, ']') + if len(name) != 0 { + fmt.Fprintf(&template, "{%s}", name) + info.Vars[name] = value + } + } + + info.Template = template.String() + + return &info +} + +func readUntil(r *strings.Reader, delim byte) string { + var buff strings.Builder + for { + c, err := r.ReadByte() + if err == nil && c != delim { + buff.WriteByte(c) + } else { + break + } + } + + return buff.String() +} + +func RemoveXPATHPredicates(s string) (string, error) { + var b bytes.Buffer + for i := 0; i < len(s); { + ss := s[i:] + si, ei := strings.Index(ss, "["), strings.Index(ss, "]") + switch { + case si == -1 && ei == -1: + // This substring didn't contain a [] pair, therefore write it + // to the buffer. + b.WriteString(ss) + // Move to the last character of the substring. + i += len(ss) + case si == -1 || ei == -1: + // This substring contained a mismatched pair of []s. + return "", fmt.Errorf("Mismatched brackets within substring %s of %s, [ pos: %d, ] pos: %d", ss, s, si, ei) + case si > ei: + // This substring contained a ] before a [. + return "", fmt.Errorf("Incorrect ordering of [] within substring %s of %s, [ pos: %d, ] pos: %d", ss, s, si, ei) + default: + // This substring contained a matched set of []s. + b.WriteString(ss[0:si]) + i += ei + 1 + } + } + + return b.String(), nil +} diff --git a/translib/transformer/xlate.go b/translib/transformer/xlate.go new file mode 100644 index 000000000..20d80a162 --- /dev/null +++ b/translib/transformer/xlate.go @@ -0,0 +1,407 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "fmt" + "encoding/json" + "errors" + log "github.com/golang/glog" + "github.com/openconfig/ygot/ygot" + "reflect" + "strings" + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" +) + +const ( + GET = 1 + iota + CREATE + REPLACE + UPDATE + DELETE +) + +type KeySpec struct { + dbNum db.DBNum + Ts db.TableSpec + Key db.Key + Child []KeySpec +} + +var XlateFuncs = make(map[string]reflect.Value) + +var ( + ErrParamsNotAdapted = errors.New("The number of params is not adapted.") +) + +func XlateFuncBind(name string, fn interface{}) (err error) { + defer func() { + if e := recover(); e != nil { + err = errors.New(name + " is not valid Xfmr function.") + } + }() + + if _, ok := XlateFuncs[name]; !ok { + v := reflect.ValueOf(fn) + v.Type().NumIn() + XlateFuncs[name] = v + } else { + log.Info("Duplicate entry found in the XlateFunc map " + name) + } + return +} + +func XlateFuncCall(name string, params ...interface{}) (result []reflect.Value, err error) { + if _, ok := XlateFuncs[name]; !ok { + err = errors.New(name + " Xfmr function does not exist.") + return nil, err + } + if len(params) != XlateFuncs[name].Type().NumIn() { + err = ErrParamsNotAdapted + return nil, nil + } + in := make([]reflect.Value, len(params)) + for k, param := range params { + in[k] = reflect.ValueOf(param) + } + result = XlateFuncs[name].Call(in) + return result, nil +} + +func TraverseDb(dbs [db.MaxDB]*db.DB, spec KeySpec, result *map[db.DBNum]map[string]map[string]db.Value, parentKey *db.Key) error { + var err error + var dbOpts db.Options + + dbOpts = getDBOptions(spec.dbNum) + separator := dbOpts.KeySeparator + log.Infof("key separator for table %v in Db %v is %v", spec.Ts.Name, spec.dbNum, separator) + + if spec.Key.Len() > 0 { + // get an entry with a specific key + data, err := dbs[spec.dbNum].GetEntry(&spec.Ts, spec.Key) + if err != nil { + return err + } + + if (*result)[spec.dbNum][spec.Ts.Name] == nil { + (*result)[spec.dbNum][spec.Ts.Name] = map[string]db.Value{strings.Join(spec.Key.Comp, separator): data} + } else { + (*result)[spec.dbNum][spec.Ts.Name][strings.Join(spec.Key.Comp, separator)] = data + } + + if len(spec.Child) > 0 { + for _, ch := range spec.Child { + err = TraverseDb(dbs, ch, result, &spec.Key) + } + } + } else { + // TODO - GetEntry support with regex patten, 'abc*' for optimization + keys, err := dbs[spec.dbNum].GetKeys(&spec.Ts) + if err != nil { + return err + } + for i, _ := range keys { + if parentKey != nil { + // TODO - multi-depth with a custom delimiter + if strings.Index(strings.Join(keys[i].Comp, separator), strings.Join((*parentKey).Comp, separator)) == -1 { + continue + } + } + spec.Key = keys[i] + err = TraverseDb(dbs, spec, result, parentKey) + } + } + return err +} + +func XlateUriToKeySpec(uri string, ygRoot *ygot.GoStruct, t *interface{}) (*[]KeySpec, error) { + + var err error + var retdbFormat = make([]KeySpec, 0) + + // In case of SONIC yang, the tablename and key info is available in the xpath + if isSonicYang(uri) { + /* Extract the xpath and key from input xpath */ + xpath, keyStr, tableName := sonicXpathKeyExtract(uri) + retdbFormat = fillSonicKeySpec(xpath, tableName, keyStr) + } else { + /* Extract the xpath and key from input xpath */ + xpath, keyStr, _ := xpathKeyExtract(nil, ygRoot, 0, uri) + retdbFormat = FillKeySpecs(xpath, keyStr, &retdbFormat) + } + + return &retdbFormat, err +} + +func FillKeySpecs(yangXpath string , keyStr string, retdbFormat *[]KeySpec) ([]KeySpec){ + if xYangSpecMap == nil { + return *retdbFormat + } + _, ok := xYangSpecMap[yangXpath] + if ok { + xpathInfo := xYangSpecMap[yangXpath] + if xpathInfo.tableName != nil { + dbFormat := KeySpec{} + dbFormat.Ts.Name = *xpathInfo.tableName + dbFormat.dbNum = xpathInfo.dbIndex + if keyStr != "" { + dbFormat.Key.Comp = append(dbFormat.Key.Comp, keyStr) + } + for _, child := range xpathInfo.childTable { + if xDbSpecMap != nil { + if _, ok := xDbSpecMap[child]; ok { + chlen := len(xDbSpecMap[child].yangXpath) + if chlen > 0 { + children := make([]KeySpec, 0) + for _, childXpath := range xDbSpecMap[child].yangXpath { + children = FillKeySpecs(childXpath, "", &children) + dbFormat.Child = append(dbFormat.Child, children...) + } + } + } + } + } + *retdbFormat = append(*retdbFormat, dbFormat) + } else { + for _, child := range xpathInfo.childTable { + if xDbSpecMap != nil { + if _, ok := xDbSpecMap[child]; ok { + chlen := len(xDbSpecMap[child].yangXpath) + if chlen > 0 { + for _, childXpath := range xDbSpecMap[child].yangXpath { + *retdbFormat = FillKeySpecs(childXpath, "", retdbFormat) + } + } + } + } + } + } + } + return *retdbFormat +} + +func fillSonicKeySpec(xpath string , tableName string, keyStr string) ( []KeySpec ) { + + var retdbFormat = make([]KeySpec, 0) + + if tableName != "" { + dbFormat := KeySpec{} + dbFormat.Ts.Name = tableName + cdb := db.ConfigDB + if _, ok := xDbSpecMap[tableName]; ok { + cdb = xDbSpecMap[tableName].dbIndex + } + dbFormat.dbNum = cdb + if keyStr != "" { + dbFormat.Key.Comp = append(dbFormat.Key.Comp, keyStr) + } + retdbFormat = append(retdbFormat, dbFormat) + } else { + // If table name not available in xpath get top container name + container := xpath + if xDbSpecMap != nil { + if _, ok := xDbSpecMap[container]; ok { + dbInfo := xDbSpecMap[container] + if dbInfo.fieldType == "container" { + for dir, _ := range dbInfo.dbEntry.Dir { + _, ok := xDbSpecMap[dir] + if ok && xDbSpecMap[dir].dbEntry.Node.Statement().Keyword == "container" { + cdb := xDbSpecMap[dir].dbIndex + dbFormat := KeySpec{} + dbFormat.Ts.Name = dir + dbFormat.dbNum = cdb + retdbFormat = append(retdbFormat, dbFormat) + } + } + } + } + } + } + return retdbFormat +} + +func XlateToDb(path string, opcode int, d *db.DB, yg *ygot.GoStruct, yt *interface{}) (map[string]map[string]db.Value, error) { + + var err error + + device := (*yg).(*ocbinds.Device) + jsonStr, err := ygot.EmitJSON(device, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + Indent: " ", + SkipValidation: true, + RFC7951Config: &ygot.RFC7951JSONConfig{ + AppendModuleName: true, + }, + }) + + jsonData := make(map[string]interface{}) + err = json.Unmarshal([]byte(jsonStr), &jsonData) + if err != nil { + log.Errorf("Error: failed to unmarshal json.") + return nil, err + } + + // Map contains table.key.fields + var result = make(map[string]map[string]db.Value) + switch opcode { + case CREATE: + log.Info("CREATE case") + err = dbMapCreate(d, yg, opcode, path, jsonData, result) + if err != nil { + log.Errorf("Error: Data translation from yang to db failed for create request.") + } + + case UPDATE: + log.Info("UPDATE case") + err = dbMapUpdate(d, yg, opcode, path, jsonData, result) + if err != nil { + log.Errorf("Error: Data translation from yang to db failed for update request.") + } + + case REPLACE: + log.Info("REPLACE case") + err = dbMapUpdate(d, yg, opcode, path, jsonData, result) + if err != nil { + log.Errorf("Error: Data translation from yang to db failed for replace request.") + } + + case DELETE: + log.Info("DELETE case") + err = dbMapDelete(d, yg, opcode, path, jsonData, result) + if err != nil { + log.Errorf("Error: Data translation from yang to db failed for delete request.") + } + } + return result, err +} + +func GetAndXlateFromDB(uri string, ygRoot *ygot.GoStruct, dbs [db.MaxDB]*db.DB) ([]byte, error) { + var err error + var payload []byte + log.Info("received xpath =", uri) + + keySpec, err := XlateUriToKeySpec(uri, ygRoot, nil) + var dbresult = make(map[db.DBNum]map[string]map[string]db.Value) + for i := db.ApplDB; i < db.MaxDB; i++ { + dbresult[i] = make(map[string]map[string]db.Value) + } + + for _, spec := range *keySpec { + err := TraverseDb(dbs, spec, &dbresult, nil) + if err != nil { + log.Error("TraverseDb() failure") + return payload, err + } + } + + payload, err = XlateFromDb(uri, ygRoot, dbs, dbresult) + if err != nil { + log.Error("XlateFromDb() failure.") + return payload, err + } + + return payload, err +} + +func XlateFromDb(uri string, ygRoot *ygot.GoStruct, dbs [db.MaxDB]*db.DB, data map[db.DBNum]map[string]map[string]db.Value) ([]byte, error) { + + var err error + var result []byte + var dbData = make(map[db.DBNum]map[string]map[string]db.Value) + var cdb db.DBNum = db.ConfigDB + + dbData = data + if isSonicYang(uri) { + xpath, keyStr, tableName := sonicXpathKeyExtract(uri) + if (tableName != "") { + dbInfo, ok := xDbSpecMap[tableName] + if !ok { + log.Warningf("No entry in xDbSpecMap for xpath %v", tableName) + } else { + cdb = dbInfo.dbIndex + } + tokens:= strings.Split(xpath, "/") + // Format /module:container/tableName/listname[key]/fieldName + if tokens[SONIC_TABLE_INDEX] == tableName { + fieldName := tokens[len(tokens)-1] + dbSpecField := tableName + "/" + fieldName + _, ok := xDbSpecMap[dbSpecField] + if ok && xDbSpecMap[dbSpecField].fieldType == "leaf" { + dbData[cdb] = extractFieldFromDb(tableName, keyStr, fieldName, data[cdb]) + } + } + } + } else { + xpath, _ := XfmrRemoveXPATHPredicates(uri) + if _, ok := xYangSpecMap[xpath]; ok { + cdb = xYangSpecMap[xpath].dbIndex + } + } + payload, err := dbDataToYangJsonCreate(uri, ygRoot, dbs, &dbData, cdb) + log.Info("Payload generated:", payload) + + if err != nil { + log.Errorf("Error: failed to create json response from DB data.") + return nil, err + } + + result = []byte(payload) + return result, err + +} + +func extractFieldFromDb(tableName string, keyStr string, fieldName string, data map[string]map[string]db.Value) (map[string]map[string]db.Value) { + + var dbVal db.Value + var dbData = make(map[string]map[string]db.Value) + + if tableName != "" && keyStr != "" && fieldName != "" { + if data[tableName][keyStr].Field != nil { + dbData[tableName] = make(map[string]db.Value) + dbVal.Field = make(map[string]string) + dbVal.Field[fieldName] = data[tableName][keyStr].Field[fieldName] + dbData[tableName][keyStr] = dbVal + } + } + return dbData +} + +func GetModuleNmFromPath(uri string) (string, error) { + log.Infof("received uri %s to extract module name from ", uri) + moduleNm, err := uriModuleNameGet(uri) + return moduleNm, err +} + +func GetOrdDBTblList(ygModuleNm string) ([]string, error) { + var result []string + var err error + if dbTblList, ok := xDbSpecOrdTblMap[ygModuleNm]; ok { + result = dbTblList + if len(dbTblList) == 0 { + log.Error("Ordered DB Table list is empty for module name = ", ygModuleNm) + err = fmt.Errorf("Ordered DB Table list is empty for module name %v", ygModuleNm) + + } + } else { + log.Error("No entry found in the map of module names to ordered list of DB Tables for module = ", ygModuleNm) + err = fmt.Errorf("No entry found in the map of module names to ordered list of DB Tables for module = %v", ygModuleNm) + } + return result, err +} diff --git a/translib/transformer/xlate_from_db.go b/translib/transformer/xlate_from_db.go new file mode 100644 index 000000000..949f6c27c --- /dev/null +++ b/translib/transformer/xlate_from_db.go @@ -0,0 +1,766 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "fmt" + "github.com/Azure/sonic-mgmt-common/translib/db" + "strings" + "encoding/json" + "os" + "strconv" + "errors" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/ygot" + "github.com/openconfig/ygot/ytypes" + + log "github.com/golang/glog" +) + +type typeMapOfInterface map[string]interface{} + +func xfmrHandlerFunc(inParams XfmrParams) (map[string]interface{}, error) { + result := make(map[string]interface{}) + xpath, _ := XfmrRemoveXPATHPredicates(inParams.uri) + log.Infof("Subtree transformer function(\"%v\") invoked for yang path(\"%v\").", xYangSpecMap[xpath].xfmrFunc, xpath) + _, err := XlateFuncCall(dbToYangXfmrFunc(xYangSpecMap[xpath].xfmrFunc), inParams) + if err != nil { + log.Infof("Failed to retrieve data for xpath(\"%v\") err(%v).", inParams.uri, err) + return result, err + } + + ocbSch, _ := ocbinds.Schema() + schRoot := ocbSch.RootSchema() + device := (*inParams.ygRoot).(*ocbinds.Device) + + path, _ := ygot.StringToPath(inParams.uri, ygot.StructuredPath, ygot.StringSlicePath) + for _, p := range path.Elem { + pathSlice := strings.Split(p.Name, ":") + p.Name = pathSlice[len(pathSlice)-1] + if len(p.Key) > 0 { + for ekey, ent := range p.Key { + eslice := strings.Split(ent, ":") + p.Key[ekey] = eslice[len(eslice)-1] + } + } + } + + nodeList, nodeErr := ytypes.GetNode(schRoot, device, path) + if nodeErr != nil { + log.Infof("Failed to get node for xpath(\"%v\") err(%v).", inParams.uri, err) + return result, err + } + node := nodeList[0].Data + nodeYgot, _ := (node).(ygot.ValidatedGoStruct) + payload, err := ygot.EmitJSON(nodeYgot, &ygot.EmitJSONConfig{ Format: ygot.RFC7951, + Indent: " ", SkipValidation: true, + RFC7951Config: &ygot.RFC7951JSONConfig{ AppendModuleName: false, }, + }) + err = json.Unmarshal([]byte(payload), &result) + return result, err +} + +func leafXfmrHandlerFunc(inParams XfmrParams) (map[string]interface{}, error) { + xpath, _ := XfmrRemoveXPATHPredicates(inParams.uri) + ret, err := XlateFuncCall(dbToYangXfmrFunc(xYangSpecMap[xpath].xfmrFunc), inParams) + if err != nil { + return nil, err + } + if ret != nil { + fldValMap := ret[0].Interface().(map[string]interface{}) + return fldValMap, nil + } else { + return nil, nil + } +} + +func validateHandlerFunc(inParams XfmrParams) (bool) { + xpath, _ := XfmrRemoveXPATHPredicates(inParams.uri) + ret, err := XlateFuncCall(xYangSpecMap[xpath].validateFunc, inParams) + if err != nil { + return false + } + return ret[0].Interface().(bool) +} + +func xfmrTblHandlerFunc(xfmrTblFunc string, inParams XfmrParams) []string { + ret, err := XlateFuncCall(xfmrTblFunc, inParams) + if err != nil { + return []string{} + } + return ret[0].Interface().([]string) +} + + +func DbValToInt(dbFldVal string, base int, size int, isUint bool) (interface{}, error) { + var res interface{} + var err error + if isUint { + if res, err = strconv.ParseUint(dbFldVal, base, size); err != nil { + log.Warningf("Non Yint%v type for yang leaf-list item %v", size, dbFldVal) + } + } else { + if res, err = strconv.ParseInt(dbFldVal, base, size); err != nil { + log.Warningf("Non Yint %v type for yang leaf-list item %v", size, dbFldVal) + } + } + return res, err +} + +func DbToYangType(yngTerminalNdDtType yang.TypeKind, fldXpath string, dbFldVal string) (interface{}, error) { + log.Infof("Received FieldXpath %v, yngTerminalNdDtType %v and Db field value %v to be converted to yang data-type.", fldXpath, yngTerminalNdDtType, dbFldVal) + var res interface{} + var err error + const INTBASE = 10 + switch yngTerminalNdDtType { + case yang.Ynone: + log.Warning("Yang node data-type is non base yang type") + //TODO - enhance to handle non base data types depending on future use case + err = errors.New("Yang node data-type is non base yang type") + case yang.Yint8: + res, err = DbValToInt(dbFldVal, INTBASE, 8, false) + case yang.Yint16: + res, err = DbValToInt(dbFldVal, INTBASE, 16, false) + case yang.Yint32: + res, err = DbValToInt(dbFldVal, INTBASE, 32, false) + case yang.Yuint8: + res, err = DbValToInt(dbFldVal, INTBASE, 8, true) + case yang.Yuint16: + res, err = DbValToInt(dbFldVal, INTBASE, 16, true) + case yang.Yuint32: + res, err = DbValToInt(dbFldVal, INTBASE, 32, true) + case yang.Ybool: + if res, err = strconv.ParseBool(dbFldVal); err != nil { + log.Warningf("Non Bool type for yang leaf-list item %v", dbFldVal) + } + case yang.Ybinary, yang.Ydecimal64, yang.Yenum, yang.Yidentityref, yang.Yint64, yang.Yuint64, yang.Ystring, yang.Yunion,yang.Yleafref: + // TODO - handle the union type + // Make sure to encode as string, expected by util_types.go: ytypes.yangToJSONType + log.Info("Yenum/Ystring/Yunion(having all members as strings) type for yangXpath ", fldXpath) + res = dbFldVal + case yang.Yempty: + logStr := fmt.Sprintf("Yang data type for xpath %v is Yempty.", fldXpath) + log.Error(logStr) + err = errors.New(logStr) + default: + logStr := fmt.Sprintf("Unrecognized/Unhandled yang-data type(%v) for xpath %v.", fldXpath, yang.TypeKindToName[yngTerminalNdDtType]) + log.Error(logStr) + err = errors.New(logStr) + } + return res, err +} + +/*convert leaf-list in Db to leaf-list in yang*/ +func processLfLstDbToYang(fieldXpath string, dbFldVal string) []interface{} { + valLst := strings.Split(dbFldVal, ",") + var resLst []interface{} + const INTBASE = 10 + yngTerminalNdDtType := xDbSpecMap[fieldXpath].dbEntry.Type.Kind + switch yngTerminalNdDtType { + case yang.Yenum, yang.Ystring, yang.Yunion, yang.Yleafref: + // TODO handle leaf-ref base type + log.Info("DB leaf-list and Yang leaf-list are of same data-type") + for _, fldVal := range valLst { + resLst = append(resLst, fldVal) + } + default: + for _, fldVal := range valLst { + resVal, err := DbToYangType(yngTerminalNdDtType, fieldXpath, fldVal) + if err == nil { + resLst = append(resLst, resVal) + } + } + } + return resLst +} + +func sonicDbToYangTerminalNodeFill(tblName string, field string, value string, resultMap map[string]interface{}) { + resField := field + if len(value) == 0 { + return + } + if strings.HasSuffix(field, "@") { + fldVals := strings.Split(field, "@") + resField = fldVals[0] + } + fieldXpath := tblName + "/" + resField + xDbSpecMapEntry, ok := xDbSpecMap[fieldXpath] + if !ok { + log.Warningf("No entry found in xDbSpecMap for xpath %v", fieldXpath) + return + } + if xDbSpecMapEntry.dbEntry == nil { + log.Warningf("Yang entry is nil in xDbSpecMap for xpath %v", fieldXpath) + return + } + + yangType := yangTypeGet(xDbSpecMapEntry.dbEntry) + if yangType == YANG_LEAF_LIST { + /* this should never happen but just adding for safetty */ + if !strings.HasSuffix(field, "@") { + log.Warningf("Leaf-list in Sonic yang should also be a leaf-list in DB, its not for xpath %v", fieldXpath) + return + } + resLst := processLfLstDbToYang(fieldXpath, value) + resultMap[resField] = resLst + } else { /* yangType is leaf - there are only 2 types of yang terminal node leaf and leaf-list */ + yngTerminalNdDtType := xDbSpecMapEntry.dbEntry.Type.Kind + resVal, err := DbToYangType(yngTerminalNdDtType, fieldXpath, value) + if err != nil { + log.Warningf("Failure in converting Db value type to yang type for xpath", fieldXpath) + } else { + resultMap[resField] = resVal + } + } + return +} + +func sonicDbToYangListFill(uri string, xpath string, dbIdx db.DBNum, table string, key string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value) []typeMapOfInterface { + var mapSlice []typeMapOfInterface + dbTblData := (*dbDataMap)[dbIdx][table] + + for keyStr, _ := range dbTblData { + curMap := make(map[string]interface{}) + sonicDbToYangDataFill(uri, xpath, dbIdx, table, keyStr, dbDataMap, curMap) + dbSpecData, ok := xDbSpecMap[table] + if ok && dbSpecData.keyName == nil { + yangKeys := yangKeyFromEntryGet(xDbSpecMap[xpath].dbEntry) + sonicKeyDataAdd(dbIdx, yangKeys, keyStr, curMap) + } + if curMap != nil { + mapSlice = append(mapSlice, curMap) + } + } + return mapSlice +} + +func sonicDbToYangDataFill(uri string, xpath string, dbIdx db.DBNum, table string, key string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, resultMap map[string]interface{}) { + yangNode, ok := xDbSpecMap[xpath] + + if ok && yangNode.dbEntry != nil { + xpathPrefix := table + if len(table) > 0 { xpathPrefix += "/" } + + for yangChldName := range yangNode.dbEntry.Dir { + chldXpath := xpathPrefix+yangChldName + if xDbSpecMap[chldXpath] != nil && xDbSpecMap[chldXpath].dbEntry != nil { + chldYangType := yangTypeGet(xDbSpecMap[chldXpath].dbEntry) + + if chldYangType == YANG_LEAF || chldYangType == YANG_LEAF_LIST { + log.Infof("tbl(%v), k(%v), yc(%v)", table, key, yangChldName) + fldName := yangChldName + if chldYangType == YANG_LEAF_LIST { + fldName = fldName + "@" + } + sonicDbToYangTerminalNodeFill(table, fldName, (*dbDataMap)[dbIdx][table][key].Field[fldName], resultMap) + } else if chldYangType == YANG_CONTAINER { + curMap := make(map[string]interface{}) + curUri := xpath + "/" + yangChldName + // container can have a static key, so extract key for current container + _, curKey, curTable := sonicXpathKeyExtract(curUri) + // use table-name as xpath from now on + sonicDbToYangDataFill(curUri, curTable, xDbSpecMap[curTable].dbIndex, curTable, curKey, dbDataMap, curMap) + if len(curMap) > 0 { + resultMap[yangChldName] = curMap + } else { + log.Infof("Empty container for xpath(%v)", curUri) + } + } else if chldYangType == YANG_LIST { + var mapSlice []typeMapOfInterface + curUri := xpath + "/" + yangChldName + mapSlice = sonicDbToYangListFill(curUri, curUri, dbIdx, table, key, dbDataMap) + if len(key) > 0 && len(mapSlice) == 1 {// Single instance query. Don't return array of maps + for k, val := range mapSlice[0] { + resultMap[k] = val + } + + } else if len(mapSlice) > 0 { + resultMap[yangChldName] = mapSlice + } else { + log.Infof("Empty list for xpath(%v)", curUri) + } + } + } + } + } + return +} + +/* Traverse db map and create json for cvl yang */ +func directDbToYangJsonCreate(uri string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, resultMap map[string]interface{}) (string, error) { + xpath, key, table := sonicXpathKeyExtract(uri) + + if len(xpath) > 0 { + var dbNode *dbInfo + + if len(table) > 0 { + tokens:= strings.Split(xpath, "/") + if tokens[SONIC_TABLE_INDEX] == table { + fieldName := tokens[len(tokens)-1] + dbSpecField := table + "/" + fieldName + _, ok := xDbSpecMap[dbSpecField] + if ok && (xDbSpecMap[dbSpecField].fieldType == YANG_LEAF || xDbSpecMap[dbSpecField].fieldType == YANG_LEAF_LIST) { + dbNode = xDbSpecMap[dbSpecField] + xpath = dbSpecField + } else { + dbNode = xDbSpecMap[table] + } + } + } else { + dbNode, _ = xDbSpecMap[xpath] + } + + if dbNode != nil && dbNode.dbEntry != nil { + cdb := db.ConfigDB + yangType := yangTypeGet(dbNode.dbEntry) + if len(table) > 0 { + cdb = xDbSpecMap[table].dbIndex + } + + if yangType == YANG_LEAF || yangType == YANG_LEAF_LIST { + fldName := xDbSpecMap[xpath].dbEntry.Name + if yangType == YANG_LEAF_LIST { + fldName = fldName + "@" + } + sonicDbToYangTerminalNodeFill(table, fldName, (*dbDataMap)[cdb][table][key].Field[fldName], resultMap) + } else if yangType == YANG_CONTAINER { + if len(table) > 0 { + xpath = table + } + sonicDbToYangDataFill(uri, xpath, cdb, table, key, dbDataMap, resultMap) + } else if yangType == YANG_LIST { + mapSlice := sonicDbToYangListFill(uri, xpath, cdb, table, key, dbDataMap) + if len(key) > 0 && len(mapSlice) == 1 {// Single instance query. Don't return array of maps + for k, val := range mapSlice[0] { + resultMap[k] = val + } + + } else if len(mapSlice) > 0 { + pathl := strings.Split(xpath, "/") + lname := pathl[len(pathl) - 1] + resultMap[lname] = mapSlice + } + } + } + } + + jsonMapData, _ := json.Marshal(resultMap) + jsonData := fmt.Sprintf("%v", string(jsonMapData)) + jsonDataPrint(jsonData) + return jsonData, nil +} + +func tableNameAndKeyFromDbMapGet(dbDataMap map[string]map[string]db.Value) (string, string, error) { + tableName := "" + tableKey := "" + for tn, tblData := range dbDataMap { + tableName = tn + for kname, _ := range tblData { + tableKey = kname + } + } + return tableName, tableKey, nil +} + +func fillDbDataMapForTbl(uri string, xpath string, tblName string, tblKey string, cdb db.DBNum, dbs [db.MaxDB]*db.DB) (map[db.DBNum]map[string]map[string]db.Value, error) { + var err error + dbresult := make(map[db.DBNum]map[string]map[string]db.Value) + dbresult[cdb] = make(map[string]map[string]db.Value) + dbFormat := KeySpec{} + dbFormat.Ts.Name = tblName + dbFormat.dbNum = cdb + if tblKey != "" { + dbFormat.Key.Comp = append(dbFormat.Key.Comp, tblKey) + } + err = TraverseDb(dbs, dbFormat, &dbresult, nil) + if err != nil { + log.Errorf("TraverseDb() failure for tbl(DB num) %v(%v) for xpath %v", tblName, cdb, xpath) + return nil, err + } + if _, ok := dbresult[cdb]; !ok { + logStr := fmt.Sprintf("TraverseDb() did not populate Db data for tbl(DB num) %v(%v) for xpath %v", tblName, cdb, xpath) + err = fmt.Errorf("%v", logStr) + return nil, err + } + return dbresult, err + +} + +// Assumption: All tables are from the same DB +func dbDataFromTblXfmrGet(tbl string, inParams XfmrParams, dbDataMap *map[db.DBNum]map[string]map[string]db.Value) error { + xpath, _ := XfmrRemoveXPATHPredicates(inParams.uri) + curDbDataMap, err := fillDbDataMapForTbl(inParams.uri, xpath, tbl, inParams.key, inParams.curDb, inParams.dbs) + if err == nil { + mapCopy((*dbDataMap)[inParams.curDb], curDbDataMap[inParams.curDb]) + } + return nil +} + +func yangListDataFill(dbs [db.MaxDB]*db.DB, ygRoot *ygot.GoStruct, uri string, xpath string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, resultMap map[string]interface{}, tbl string, tblKey string, cdb db.DBNum, validate bool) error { + var tblList []string + + if tbl == "" && xYangSpecMap[xpath].xfmrTbl != nil { + xfmrTblFunc := *xYangSpecMap[xpath].xfmrTbl + if len(xfmrTblFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, tblKey, dbDataMap, nil) + tblList = xfmrTblHandlerFunc(xfmrTblFunc, inParams) + if len(tblList) != 0 { + for _, curTbl := range tblList { + dbDataFromTblXfmrGet(curTbl, inParams, dbDataMap) + } + } + } + } else if tbl != "" && xYangSpecMap[xpath].xfmrTbl == nil { + tblList = append(tblList, tbl) + } else if tbl != "" && xYangSpecMap[xpath].xfmrTbl != nil { + /*key instance level GET, table name and table key filled from xpathKeyExtract which internally calls table transformer*/ + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, tblKey, dbDataMap, nil) + dbDataFromTblXfmrGet(tbl, inParams, dbDataMap) + tblList = append(tblList, tbl) + + } + + for _, tbl = range(tblList) { + tblData, ok := (*dbDataMap)[cdb][tbl] + + if ok { + var mapSlice []typeMapOfInterface + for dbKey, _ := range tblData { + curMap := make(map[string]interface{}) + curKeyMap, curUri, _ := dbKeyToYangDataConvert(uri, xpath, dbKey, dbs[cdb].Opts.KeySeparator) + if len(xYangSpecMap[xpath].xfmrFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, curUri, GET, "", dbDataMap, nil) + cmap, _ := xfmrHandlerFunc(inParams) + if cmap != nil && len(cmap) > 0 { + mapSlice = append(mapSlice, curMap) + } else { + log.Infof("Empty container returned from overloaded transformer for(\"%v\")", curUri) + } + } else { + _, keyFromCurUri, _ := xpathKeyExtract(dbs[cdb], ygRoot, GET, curUri) + if dbKey == keyFromCurUri { + for k, kv := range curKeyMap { + curMap[k] = kv + } + curXpath, _ := XfmrRemoveXPATHPredicates(curUri) + yangDataFill(dbs, ygRoot, curUri, curXpath, dbDataMap, curMap, tbl, dbKey, cdb, validate) + mapSlice = append(mapSlice, curMap) + } + } + } + if len(mapSlice) > 0 { + resultMap[xYangSpecMap[xpath].yangEntry.Name] = mapSlice + } else { + log.Infof("Empty slice for (\"%v\").\r\n", uri) + } + } + }// end of tblList for + return nil +} + +func terminalNodeProcess(dbs [db.MaxDB]*db.DB, ygRoot *ygot.GoStruct, uri string, xpath string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, tbl string, tblKey string) (map[string]interface{}, error) { + log.Infof("Received xpath - %v, uri - %v, table - %v, table key - %v", xpath, uri, tbl, tblKey) + var err error + resFldValMap := make(map[string]interface{}) + if xYangSpecMap[xpath].yangEntry == nil { + logStr := fmt.Sprintf("No yang entry found for xpath %v.", xpath) + err = fmt.Errorf("%v", logStr) + return resFldValMap, err + } + + cdb := xYangSpecMap[xpath].dbIndex + if len(xYangSpecMap[xpath].xfmrFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, tblKey, dbDataMap, nil) + fldValMap, err := leafXfmrHandlerFunc(inParams) + if err != nil { + logStr := fmt.Sprintf("%Failed to get data from overloaded function for %v -v.", uri, err) + err = fmt.Errorf("%v", logStr) + return resFldValMap, err + } + if fldValMap != nil { + for lf, val := range fldValMap { + resFldValMap[lf] = val + } + } + } else { + dbFldName := xYangSpecMap[xpath].fieldName + if dbFldName == "NONE" { + return resFldValMap, err + } + /* if there is no transformer extension/annotation then it means leaf-list in yang is also leaflist in db */ + if len(dbFldName) > 0 && !xYangSpecMap[xpath].isKey { + yangType := yangTypeGet(xYangSpecMap[xpath].yangEntry) + if yangType == YANG_LEAF_LIST { + dbFldName += "@" + val, ok := (*dbDataMap)[cdb][tbl][tblKey].Field[dbFldName] + if ok { + resLst := processLfLstDbToYang(xpath, val) + resFldValMap[xYangSpecMap[xpath].yangEntry.Name] = resLst + } + } else { + val, ok := (*dbDataMap)[cdb][tbl][tblKey].Field[dbFldName] + if ok { + yngTerminalNdDtType := xYangSpecMap[xpath].yangEntry.Type.Kind + resVal, err := DbToYangType(yngTerminalNdDtType, xpath, val) + if err != nil { + log.Error("Failure in converting Db value type to yang type for field", xpath) + } else { + resFldValMap[xYangSpecMap[xpath].yangEntry.Name] = resVal + } + } + } + } + } + return resFldValMap, err +} + +func yangDataFill(dbs [db.MaxDB]*db.DB, ygRoot *ygot.GoStruct, uri string, xpath string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, resultMap map[string]interface{}, tbl string, tblKey string, cdb db.DBNum, validate bool) error { + var err error + isValid := validate + yangNode, ok := xYangSpecMap[xpath] + + if ok && yangNode.yangEntry != nil { + for yangChldName := range yangNode.yangEntry.Dir { + chldXpath := xpath+"/"+yangChldName + chldUri := uri+"/"+yangChldName + if xYangSpecMap[chldXpath] != nil && xYangSpecMap[chldXpath].yangEntry != nil { + cdb = xYangSpecMap[chldXpath].dbIndex + if len(xYangSpecMap[chldXpath].validateFunc) > 0 && !validate { + _, key, _ := xpathKeyExtract(dbs[cdb], ygRoot, GET, chldUri) + // TODO - handle non CONFIG-DB + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, chldUri, GET, key, dbDataMap, nil) + res := validateHandlerFunc(inParams) + if res != true { + continue + } else { + isValid = res + } + } + chldYangType := yangTypeGet(xYangSpecMap[chldXpath].yangEntry) + if chldYangType == YANG_LEAF || chldYangType == YANG_LEAF_LIST { + fldValMap, err := terminalNodeProcess(dbs, ygRoot, chldUri, chldXpath, dbDataMap, tbl, tblKey) + if err != nil { + log.Infof("Failed to get data(\"%v\").", chldUri) + } + for lf, val := range fldValMap { + resultMap[lf] = val + } + } else if chldYangType == YANG_CONTAINER { + cname := xYangSpecMap[chldXpath].yangEntry.Name + if xYangSpecMap[chldXpath].xfmrTbl != nil { + xfmrTblFunc := *xYangSpecMap[chldXpath].xfmrTbl + if len(xfmrTblFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, chldUri, GET, tblKey, dbDataMap, nil) + tblList := xfmrTblHandlerFunc(xfmrTblFunc, inParams) + if len(tblList) > 1 { + log.Warningf("Table transformer returned more than one table for container %v", chldXpath) + } + if len(tblList) == 0 { + continue + } + dbDataFromTblXfmrGet(tblList[0], inParams, dbDataMap) + tbl = tblList[0] + } + } + if len(xYangSpecMap[chldXpath].xfmrFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, chldUri, GET, "", dbDataMap, nil) + cmap, _ := xfmrHandlerFunc(inParams) + if cmap != nil && len(cmap) > 0 { + resultMap[cname] = cmap + } else { + log.Infof("Empty container(\"%v\").\r\n", chldUri) + } + continue + } else { + cmap := make(map[string]interface{}) + err = yangDataFill(dbs, ygRoot, chldUri, chldXpath, dbDataMap, cmap, tbl, tblKey, cdb, isValid) + if len(cmap) > 0 { + resultMap[cname] = cmap + } else { + log.Infof("Empty container(\"%v\").\r\n", chldUri) + } + } + } else if chldYangType == YANG_LIST { + cdb = xYangSpecMap[chldXpath].dbIndex + if len(xYangSpecMap[chldXpath].xfmrFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, chldUri, GET, "", dbDataMap, nil) + cmap, _ := xfmrHandlerFunc(inParams) + if cmap != nil && len(cmap) > 0 { + resultMap = cmap + } else { + log.Infof("Empty list(\"%v\").\r\n", chldUri) + } + } else { + ynode, ok := xYangSpecMap[chldXpath] + lTblName := "" + if ok && ynode.tableName != nil { + lTblName = *ynode.tableName + } + yangListDataFill(dbs, ygRoot, chldUri, chldXpath, dbDataMap, resultMap, lTblName, "", cdb, isValid) + } + } else { + return err + } + } + } + } + return err +} + +/* Traverse linear db-map data and add to nested json data */ +func dbDataToYangJsonCreate(uri string, ygRoot *ygot.GoStruct, dbs [db.MaxDB]*db.DB, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, cdb db.DBNum) (string, error) { + var err error + jsonData := "" + resultMap := make(map[string]interface{}) + if isSonicYang(uri) { + return directDbToYangJsonCreate(uri, dbDataMap, resultMap) + } else { + var d *db.DB + reqXpath, keyName, tableName := xpathKeyExtract(d, ygRoot, GET, uri) + yangNode, ok := xYangSpecMap[reqXpath] + if ok { + yangType := yangTypeGet(yangNode.yangEntry) + validateHandlerFlag := false + tableXfmrFlag := false + IsValidate := false + if len(xYangSpecMap[reqXpath].validateFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, keyName, dbDataMap, nil) + res := validateHandlerFunc(inParams) + if !res { + validateHandlerFlag = true + /* cannot immediately return from here since reXpath yangtype decides the return type */ + } else { + IsValidate = res + } + } + isList := false + switch yangType { + case YANG_LIST: + isList = true + case YANG_LEAF, YANG_LEAF_LIST, YANG_CONTAINER: + isList = false + default: + log.Infof("Unknown yang object type for path %v", reqXpath) + isList = true //do not want non-list processing to happen + } + /*If yangtype is a list separate code path is to be taken in case of table transformer + since that code path already handles the calling of table transformer and subsequent processing + */ + if (!validateHandlerFlag) && (!isList) { + if xYangSpecMap[reqXpath].xfmrTbl != nil { + xfmrTblFunc := *xYangSpecMap[reqXpath].xfmrTbl + if len(xfmrTblFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, keyName, dbDataMap, nil) + tblList := xfmrTblHandlerFunc(xfmrTblFunc, inParams) + if len(tblList) > 1 { + log.Warningf("Table transformer returned more than one table for container %v", reqXpath) + tableXfmrFlag = true + } + if len(tblList) == 0 { + log.Warningf("Table transformer returned no table for conatiner %v", reqXpath) + tableXfmrFlag = true + } + if !tableXfmrFlag { + dbDataFromTblXfmrGet(tblList[0], inParams, dbDataMap) + } + } else { + log.Warningf("empty table transformer function name for xpath - %v", reqXpath) + tableXfmrFlag = true + } + } + } + + for { + if yangType == YANG_LEAF || yangType == YANG_LEAF_LIST { + yangName := xYangSpecMap[reqXpath].yangEntry.Name + if validateHandlerFlag || tableXfmrFlag { + resultMap[yangName] = "" + break + } + tbl, key, _ := tableNameAndKeyFromDbMapGet((*dbDataMap)[cdb]) + fldValMap, err := terminalNodeProcess(dbs, ygRoot, uri, reqXpath, dbDataMap, tbl, key) + if err != nil { + log.Infof("Empty terminal node (\"%v\").", uri) + } + resultMap = fldValMap + break + + } else if yangType == YANG_CONTAINER { + cname := xYangSpecMap[reqXpath].yangEntry.Name + cmap := make(map[string]interface{}) + resultMap[cname] = cmap + if validateHandlerFlag || tableXfmrFlag { + break + } + if len(xYangSpecMap[reqXpath].xfmrFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, "", dbDataMap, nil) + cmap, _ = xfmrHandlerFunc(inParams) + if cmap != nil && len(cmap) > 0 { + resultMap[cname] = cmap + } + break + } + err = yangDataFill(dbs, ygRoot, uri, reqXpath, dbDataMap, resultMap, tableName, keyName, cdb, IsValidate) + if err != nil { + log.Infof("Empty container(\"%v\").\r\n", uri) + } + break + } else if yangType == YANG_LIST { + if len(xYangSpecMap[reqXpath].xfmrFunc) > 0 { + inParams := formXfmrInputRequest(dbs[cdb], dbs, cdb, ygRoot, uri, GET, "", dbDataMap, nil) + cmap, _ := xfmrHandlerFunc(inParams) + if cmap != nil && len(cmap) > 0 { + resultMap = cmap + } else { + log.Infof("Empty list(\"%v\").\r\n", uri) + } + } else { + err = yangListDataFill(dbs, ygRoot, uri, reqXpath, dbDataMap, resultMap, tableName, keyName, cdb, IsValidate) + if err != nil { + log.Infof("yangListDataFill failed for list case(\"%v\").\r\n", uri) + } + } + break + } else { + log.Warningf("Unknown yang object type for path %v", reqXpath) + break + } + } //end of for + } + } + + jsonMapData, _ := json.Marshal(resultMap) + jsonData = fmt.Sprintf("%v", string(jsonMapData)) + jsonDataPrint(jsonData) + return jsonData, nil +} + +func jsonDataPrint(data string) { + fp, err := os.Create("/tmp/dbToYangJson.txt") + if err != nil { + return + } + defer fp.Close() + + fmt.Fprintf (fp, "-----------------------------------------------------------------\r\n") + fmt.Fprintf (fp, "%v \r\n", data) + fmt.Fprintf (fp, "-----------------------------------------------------------------\r\n") +} + diff --git a/translib/transformer/xlate_to_db.go b/translib/transformer/xlate_to_db.go new file mode 100644 index 000000000..6bcccb4a6 --- /dev/null +++ b/translib/transformer/xlate_to_db.go @@ -0,0 +1,565 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "errors" + "fmt" + "github.com/openconfig/ygot/ygot" + "os" + "reflect" + "strings" + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/ocbinds" + "github.com/openconfig/ygot/ytypes" + "github.com/openconfig/goyang/pkg/yang" + + log "github.com/golang/glog" +) + +/* Invoke the post tansformer */ +func postXfmrHandlerFunc(inParams XfmrParams) (map[string]map[string]db.Value, error) { + xpath, _ := XfmrRemoveXPATHPredicates(inParams.uri) + ret, err := XlateFuncCall(xYangSpecMap[xpath].xfmrPost, inParams) + if err != nil { + return nil, err + } + retData := ret[0].Interface().(map[string]map[string]db.Value) + log.Info("Post Transformer function :", xYangSpecMap[xpath].xfmrPost, " Xpath: ", xpath, " retData: ", retData) + return retData, err +} + +/* Fill redis-db map with field & value info */ +func dataToDBMapAdd(tableName string, dbKey string, result map[string]map[string]db.Value, field string, value string) { + _, ok := result[tableName] + if !ok { + result[tableName] = make(map[string]db.Value) + } + + _, ok = result[tableName][dbKey] + if !ok { + result[tableName][dbKey] = db.Value{Field: make(map[string]string)} + } + + if field == "NONE" { + result[tableName][dbKey].Field["NULL"] = "NULL" + return + } + + result[tableName][dbKey].Field[field] = value + return +} + +func tblNameFromTblXfmrGet(xfmrTblFunc string, inParams XfmrParams) (string, error){ + tblList := xfmrTblHandlerFunc(xfmrTblFunc, inParams) + if len(tblList) != 1 { + logStr := fmt.Sprintf("Invalid return value(%v) from table transformer for (%v)", tblList, inParams.uri) + log.Error(logStr) + err := errors.New(logStr) + return "", err + } + return tblList[0], nil +} + +/* Fill the redis-db map with data */ +func mapFillData(d *db.DB, ygRoot *ygot.GoStruct, oper int, uri string, dbKey string, result map[string]map[string]db.Value, xpathPrefix string, name string, value interface{}) error { + var dbs [db.MaxDB]*db.DB + var err error + xpath := xpathPrefix + "/" + name + xpathInfo, ok := xYangSpecMap[xpath] + log.Infof("name: \"%v\", xpathPrefix(\"%v\").", name, xpathPrefix) + + if !ok || xpathInfo == nil { + log.Errorf("Yang path(\"%v\") not found.", xpath) + return errors.New("Invalid URI") + } + + if xpathInfo.tableName == nil && xpathInfo.xfmrTbl == nil{ + log.Errorf("Table for yang-path(\"%v\") not found.", xpath) + return errors.New("Invalid table name") + } + + if len(dbKey) == 0 { + log.Errorf("Table key for yang path(\"%v\") not found.", xpath) + return errors.New("Invalid table key") + } + + if xpathInfo.isKey { + return nil + } + + tableName := "" + if xpathInfo.xfmrTbl != nil { + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, uri, oper, "", nil, "") + // expecting only one table name from tbl-xfmr + tableName, err = tblNameFromTblXfmrGet(*xYangSpecMap[xpath].xfmrTbl, inParams) + if err != nil { + return err + } + } else { + tableName = *xpathInfo.tableName + } + + if len(xpathInfo.xfmrFunc) > 0 { + uri = uri + "/" + name + + /* field transformer present */ + log.Infof("Transformer function(\"%v\") invoked for yang path(\"%v\").", xpathInfo.xfmrFunc, xpath) + path, _ := ygot.StringToPath(uri, ygot.StructuredPath, ygot.StringSlicePath) + for _, p := range path.Elem { + pathSlice := strings.Split(p.Name, ":") + p.Name = pathSlice[len(pathSlice)-1] + if len(p.Key) > 0 { + for ekey, ent := range p.Key { + eslice := strings.Split(ent, ":") + p.Key[ekey] = eslice[len(eslice)-1] + } + } + } + ocbSch, _ := ocbinds.Schema() + schRoot := ocbSch.RootSchema() + node, nErr := ytypes.GetNode(schRoot, (*ygRoot).(*ocbinds.Device), path) + log.Info("GetNode data: ", node[0].Data, " nErr :", nErr) + if nErr != nil { + return nErr + } + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, uri, oper, "", nil, node[0].Data) + ret, err := XlateFuncCall(yangToDbXfmrFunc(xYangSpecMap[xpath].xfmrFunc), inParams) + if err != nil { + return err + } + if ret != nil { + retData := ret[0].Interface().(map[string]string) + log.Info("Transformer function :", xpathInfo.xfmrFunc, " Xpath: ", xpath, " retData: ", retData) + for f, v := range retData { + dataToDBMapAdd(tableName, dbKey, result, f, v) + } + } + return nil + } + + if len(xpathInfo.fieldName) == 0 { + log.Infof("Field for yang-path(\"%v\") not found in DB.", xpath) + return errors.New("Invalid field name") + } + fieldName := xpathInfo.fieldName + valueStr := "" + if xpathInfo.yangEntry.IsLeafList() { + /* Both yang side and Db side('@' suffix field) the data type is leaf-list */ + log.Info("Yang type and Db type is Leaflist for field = ", xpath) + fieldName += "@" + if reflect.ValueOf(value).Kind() != reflect.Slice { + logStr := fmt.Sprintf("Value for yang xpath %v which is a leaf-list should be a slice", xpath) + log.Error(logStr) + err := errors.New(logStr) + return err + } + valData := reflect.ValueOf(value) + for fidx := 0; fidx < valData.Len(); fidx++ { + if fidx > 0 { + valueStr += "," + } + fVal := fmt.Sprintf("%v", valData.Index(fidx).Interface()) + valueStr = valueStr + fVal + } + log.Infof("leaf-list value after conversion to DB format %v : %v", fieldName, valueStr) + + } else { // xpath is a leaf + valueStr = fmt.Sprintf("%v", value) + if strings.Contains(valueStr, ":") { + valueStr = strings.Split(valueStr, ":")[1] + } + } + + dataToDBMapAdd(tableName, dbKey, result, fieldName, valueStr) + log.Infof("TblName: \"%v\", key: \"%v\", field: \"%v\", valueStr: \"%v\".", tableName, dbKey, + fieldName, valueStr) + return nil +} + +func sonicYangReqToDbMapCreate(jsonData interface{}, result map[string]map[string]db.Value) error { + if reflect.ValueOf(jsonData).Kind() == reflect.Map { + data := reflect.ValueOf(jsonData) + for _, key := range data.MapKeys() { + _, ok := xDbSpecMap[key.String()] + if ok { + directDbMapData("", key.String(), data.MapIndex(key).Interface(), result) + } else { + sonicYangReqToDbMapCreate(data.MapIndex(key).Interface(), result) + } + } + } + return nil +} + +func dbMapDataFill(uri string, tableName string, keyName string, d map[string]interface{}, result map[string]map[string]db.Value) { + result[tableName][keyName] = db.Value{Field: make(map[string]string)} + for field, value := range d { + fieldXpath := tableName + "/" + field + if _, fieldOk := xDbSpecMap[fieldXpath]; (fieldOk && (xDbSpecMap[fieldXpath].dbEntry != nil)) { + log.Info("Found non-nil yang entry in xDbSpecMap for field xpath = ", fieldXpath) + if xDbSpecMap[fieldXpath].dbEntry.IsLeafList() { + log.Info("Yang type is Leaflist for field = ", field) + field += "@" + fieldDt := reflect.ValueOf(value) + fieldValue := "" + for fidx := 0; fidx < fieldDt.Len(); fidx++ { + if fidx > 0 { + fieldValue += "," + } + fVal := fmt.Sprintf("%v", fieldDt.Index(fidx).Interface()) + fieldValue = fieldValue + fVal + } + result[tableName][keyName].Field[field] = fieldValue + continue + } + } else { + // should ideally never happen , just adding for safety + log.Info("Did not find entry in xDbSpecMap for field xpath = ", fieldXpath) + } + result[tableName][keyName].Field[field] = fmt.Sprintf("%v", value) + } + return +} + +func dbMapListDataFill(uri string, tableName string, dbEntry *yang.Entry, jsonData interface{}, result map[string]map[string]db.Value) { + data := reflect.ValueOf(jsonData) + tblKeyName := strings.Split(dbEntry.Key, " ") + for idx := 0; idx < data.Len(); idx++ { + keyName := "" + d := data.Index(idx).Interface().(map[string]interface{}) + for i, k := range tblKeyName { + if i > 0 { + keyName += "|" + } + keyName += fmt.Sprintf("%v", d[k]) + delete(d, k) + } + dbMapDataFill(uri, tableName, keyName, d, result) + } + return +} + +func directDbMapData(uri string, tableName string, jsonData interface{}, result map[string]map[string]db.Value) bool { + _, ok := xDbSpecMap[tableName] + if ok && xDbSpecMap[tableName].dbEntry != nil { + data := reflect.ValueOf(jsonData).Interface().(map[string]interface{}) + key := "" + dbSpecData := xDbSpecMap[tableName] + result[tableName] = make(map[string]db.Value) + + if dbSpecData.keyName != nil { + key = *dbSpecData.keyName + log.Infof("Fill data for container uri(%v), key(%v)", uri, key) + dbMapDataFill(uri, tableName, key, data, result) + return true + } + + for k, v := range data { + xpath := tableName + "/" + k + curDbSpecData, ok := xDbSpecMap[xpath] + if ok && curDbSpecData.dbEntry != nil { + eType := yangTypeGet(curDbSpecData.dbEntry) + switch eType { + case "list": + log.Infof("Fill data for list uri(%v)", uri) + dbMapListDataFill(uri, tableName, curDbSpecData.dbEntry, v, result) + default: + log.Infof("Invalid node type for uri(%v)", uri) + } + } + } + } + return true +} + +/* Get the db table, key and field name for the incoming delete request */ +func dbMapDelete(d *db.DB, ygRoot *ygot.GoStruct, oper int, path string, jsonData interface{}, result map[string]map[string]db.Value) error { + var err error + if isSonicYang(path) { + xpathPrefix, keyName, tableName := sonicXpathKeyExtract(path) + log.Infof("Delete req: path(\"%v\"), key(\"%v\"), xpathPrefix(\"%v\"), tableName(\"%v\").", path, keyName, xpathPrefix, tableName) + err = sonicYangReqToDbMapDelete(xpathPrefix, tableName, keyName, result) + } else { + xpathPrefix, keyName, tableName := xpathKeyExtract(d, ygRoot, oper, path) + log.Infof("Delete req: path(\"%v\"), key(\"%v\"), xpathPrefix(\"%v\"), tableName(\"%v\").", path, keyName, xpathPrefix, tableName) + spec, ok := xYangSpecMap[xpathPrefix] + if ok { + if spec.tableName != nil { + result[*spec.tableName] = make(map[string]db.Value) + if len(keyName) > 0 { + result[*spec.tableName][keyName] = db.Value{Field: make(map[string]string)} + if spec.yangEntry != nil && spec.yangEntry.Node.Statement().Keyword == "leaf" { + result[*spec.tableName][keyName].Field[spec.fieldName] = "" + } + } + } else if len(spec.childTable) > 0 { + for _, child := range spec.childTable { + result[child] = make(map[string]db.Value) + } + } + } + } + log.Infof("Delete req: path(\"%v\") result(\"%v\").", path, result) + return err +} + +func sonicYangReqToDbMapDelete(xpathPrefix string, tableName string, keyName string, result map[string]map[string]db.Value) error { + if (tableName != "") { + // Specific table entry case + result[tableName] = make(map[string]db.Value) + if (keyName != "") { + // Specific key case + var dbVal db.Value + tokens:= strings.Split(xpathPrefix, "/") + if tokens[SONIC_TABLE_INDEX] == tableName { + fieldName := tokens[len(tokens)-1] + dbSpecField := tableName + "/" + fieldName + _, ok := xDbSpecMap[dbSpecField] + if ok { + yangType := xDbSpecMap[dbSpecField].fieldType + // terminal node case + if yangType == YANG_LEAF_LIST { + fieldName = fieldName + "@" + dbVal.Field = make(map[string]string) + dbVal.Field[fieldName] = "" + } + if yangType == YANG_LEAF { + dbVal.Field = make(map[string]string) + dbVal.Field[fieldName] = "" + } + } + } + result[tableName][keyName] = dbVal + } else { + // Get all keys + } + } else { + // Get all table entries + // If table name not available in xpath get top container name + _, ok := xDbSpecMap[xpathPrefix] + if ok && xDbSpecMap[xpathPrefix] != nil { + dbInfo := xDbSpecMap[xpathPrefix] + if dbInfo.fieldType == "container" { + for dir, _ := range dbInfo.dbEntry.Dir { + result[dir] = make(map[string]db.Value) + } + } + } + } + return nil +} + +/* Get the data from incoming update/replace request, create map and fill with dbValue(ie. field:value to write into redis-db */ +func dbMapUpdate(d *db.DB, ygRoot *ygot.GoStruct, oper int, path string, jsonData interface{}, result map[string]map[string]db.Value) error { + log.Infof("Update/replace req: path(\"%v\").", path) + dbMapCreate(d, ygRoot, oper, path, jsonData, result) + log.Infof("Update/replace req: path(\"%v\") result(\"%v\").", path, result) + printDbData(result, "/tmp/yangToDbDataUpRe.txt") + return nil +} + +/* Get the data from incoming create request, create map and fill with dbValue(ie. field:value to write into redis-db */ +func dbMapCreate(d *db.DB, ygRoot *ygot.GoStruct, oper int, path string, jsonData interface{}, result map[string]map[string]db.Value) error { + var err error + root := xpathRootNameGet(path) + if isSonicYang(path) { + err = sonicYangReqToDbMapCreate(jsonData, result) + } else { + err = yangReqToDbMapCreate(d, ygRoot, oper, root, "", "", jsonData, result) + } + if err == nil { + if oper == CREATE { + moduleNm := "/" + strings.Split(path, "/")[1] + log.Infof("Module name for path %s is %s", path, moduleNm) + if _, ok := xYangSpecMap[moduleNm]; ok { + if xYangSpecMap[moduleNm].yangDataType == "container" && len(xYangSpecMap[moduleNm].xfmrPost) > 0 { + log.Info("Invoke post transformer: ", xYangSpecMap[moduleNm].xfmrPost) + dbDataMap := make(map[db.DBNum]map[string]map[string]db.Value) + dbDataMap[db.ConfigDB] = result + var dbs [db.MaxDB]*db.DB + inParams := formXfmrInputRequest(d, dbs, db.ConfigDB, ygRoot, path, oper, "", &dbDataMap, nil) + result, err = postXfmrHandlerFunc(inParams) + } + } else { + log.Errorf("No Entry exists for module %s in xYangSpecMap. Unable to process post xfmr (\"%v\") path(\"%v\") error (\"%v\").", oper, path, err) + } + } + printDbData(result, "/tmp/yangToDbDataCreate.txt") + } else { + log.Errorf("DBMapCreate req failed for oper (\"%v\") path(\"%v\") error (\"%v\").", oper, path, err) + } + return err +} + +func yangNodeForUriGet(uri string, ygRoot *ygot.GoStruct) (interface{}, error) { + path, _ := ygot.StringToPath(uri, ygot.StructuredPath, ygot.StringSlicePath) + for _, p := range path.Elem { + pathSlice := strings.Split(p.Name, ":") + p.Name = pathSlice[len(pathSlice)-1] + if len(p.Key) > 0 { + for ekey, ent := range p.Key { + eslice := strings.Split(ent, ":") + p.Key[ekey] = eslice[len(eslice)-1] + } + } + } + ocbSch, _ := ocbinds.Schema() + schRoot := ocbSch.RootSchema() + node, nErr := ytypes.GetNode(schRoot, (*ygRoot).(*ocbinds.Device), path) + if nErr != nil { + return nil, nErr + } + return node[0].Data, nil +} + +func yangReqToDbMapCreate(d *db.DB, ygRoot *ygot.GoStruct, oper int, uri string, xpathPrefix string, keyName string, jsonData interface{}, result map[string]map[string]db.Value) error { + log.Infof("key(\"%v\"), xpathPrefix(\"%v\").", keyName, xpathPrefix) + var dbs [db.MaxDB]*db.DB + + if reflect.ValueOf(jsonData).Kind() == reflect.Slice { + log.Infof("slice data: key(\"%v\"), xpathPrefix(\"%v\").", keyName, xpathPrefix) + jData := reflect.ValueOf(jsonData) + dataMap := make([]interface{}, jData.Len()) + for idx := 0; idx < jData.Len(); idx++ { + dataMap[idx] = jData.Index(idx).Interface() + } + for _, data := range dataMap { + curKey := "" + curUri, _ := uriWithKeyCreate(uri, xpathPrefix, data) + _, ok := xYangSpecMap[xpathPrefix] + if ok && len(xYangSpecMap[xpathPrefix].xfmrKey) > 0 { + /* key transformer present */ + curYgotNode, nodeErr := yangNodeForUriGet(curUri, ygRoot) + if nodeErr != nil { + curYgotNode = nil + } + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, curUri, oper, "", nil, curYgotNode) + ret, err := XlateFuncCall(yangToDbXfmrFunc(xYangSpecMap[xpathPrefix].xfmrKey), inParams) + if err != nil { + return err + } + if ret != nil { + curKey = ret[0].Interface().(string) + } + } else if xYangSpecMap[xpathPrefix].keyName != nil { + curKey = *xYangSpecMap[xpathPrefix].keyName + } else { + curKey = keyCreate(keyName, xpathPrefix, data, d.Opts.KeySeparator) + } + yangReqToDbMapCreate(d, ygRoot, oper, curUri, xpathPrefix, curKey, data, result) + } + } else { + if reflect.ValueOf(jsonData).Kind() == reflect.Map { + jData := reflect.ValueOf(jsonData) + for _, key := range jData.MapKeys() { + typeOfValue := reflect.TypeOf(jData.MapIndex(key).Interface()).Kind() + + log.Infof("slice/map data: key(\"%v\"), xpathPrefix(\"%v\").", keyName, xpathPrefix) + xpath := uri + curUri := uri + curKey := keyName + pathAttr := key.String() + if len(xpathPrefix) > 0 { + if strings.Contains(pathAttr, ":") { + pathAttr = strings.Split(pathAttr, ":")[1] + } + xpath = xpathPrefix + "/" + pathAttr + curUri = uri + "/" + pathAttr + } + _, ok := xYangSpecMap[xpath] + log.Infof("slice/map data: curKey(\"%v\"), xpath(\"%v\"), curUri(\"%v\").", + curKey, xpath, curUri) + if ok && xYangSpecMap[xpath] != nil && len(xYangSpecMap[xpath].xfmrKey) > 0 { + /* key transformer present */ + curYgotNode, nodeErr := yangNodeForUriGet(curUri, ygRoot) + if nodeErr != nil { + curYgotNode = nil + } + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, curUri, oper, "", nil, curYgotNode) + ret, err := XlateFuncCall(yangToDbXfmrFunc(xYangSpecMap[xpath].xfmrKey), inParams) + if err != nil { + return err + } + if ret != nil { + curKey = ret[0].Interface().(string) + } + } else if xYangSpecMap[xpath].keyName != nil { + curKey = *xYangSpecMap[xpath].keyName + } + + if (typeOfValue == reflect.Map || typeOfValue == reflect.Slice) && xYangSpecMap[xpath].yangDataType != "leaf-list" { + if ok && xYangSpecMap[xpath] != nil && len(xYangSpecMap[xpath].xfmrFunc) > 0 { + /* subtree transformer present */ + curYgotNode, nodeErr := yangNodeForUriGet(curUri, ygRoot) + if nodeErr != nil { + curYgotNode = nil + } + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, curUri, oper, "", nil, curYgotNode) + ret, err := XlateFuncCall(yangToDbXfmrFunc(xYangSpecMap[xpath].xfmrFunc), inParams) + if err != nil { + return nil + } + if ret != nil { + mapCopy(result, ret[0].Interface().(map[string]map[string]db.Value)) + } + } else { + yangReqToDbMapCreate(d, ygRoot, oper, curUri, xpath, curKey, jData.MapIndex(key).Interface(), result) + } + } else { + pathAttr := key.String() + if strings.Contains(pathAttr, ":") { + pathAttr = strings.Split(pathAttr, ":")[1] + } + value := jData.MapIndex(key).Interface() + log.Infof("data field: key(\"%v\"), value(\"%v\").", key, value) + err := mapFillData(d, ygRoot, oper, uri, curKey, result, xpathPrefix, + pathAttr, value) + if err != nil { + log.Errorf("Failed constructing data for db write: key(\"%v\"), value(\"%v\"), path(\"%v\").", + pathAttr, value, xpathPrefix) + } + } + } + } + } + return nil +} + +/* Debug function to print the map data into file */ +func printDbData(db map[string]map[string]db.Value, fileName string) { + fp, err := os.Create(fileName) + if err != nil { + return + } + defer fp.Close() + + for k, v := range db { + fmt.Fprintf(fp, "-----------------------------------------------------------------\r\n") + fmt.Fprintf(fp, "table name : %v\r\n", k) + for ik, iv := range v { + fmt.Fprintf(fp, " key : %v\r\n", ik) + for k, d := range iv.Field { + fmt.Fprintf(fp, " %v :%v\r\n", k, d) + } + } + } + fmt.Fprintf(fp, "-----------------------------------------------------------------\r\n") + return +} diff --git a/translib/transformer/xlate_utils.go b/translib/transformer/xlate_utils.go new file mode 100644 index 000000000..216041609 --- /dev/null +++ b/translib/transformer/xlate_utils.go @@ -0,0 +1,591 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "fmt" + "strings" + "reflect" + "regexp" + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ygot/ygot" + log "github.com/golang/glog" +) + +/* Create db key from data xpath(request) */ +func keyCreate(keyPrefix string, xpath string, data interface{}, dbKeySep string) string { + _, ok := xYangSpecMap[xpath] + if ok { + if xYangSpecMap[xpath].yangEntry != nil { + yangEntry := xYangSpecMap[xpath].yangEntry + delim := dbKeySep + if len(xYangSpecMap[xpath].delim) > 0 { + delim = xYangSpecMap[xpath].delim + log.Infof("key concatenater(\"%v\") found for xpath %v ", delim, xpath) + } + + if len(keyPrefix) > 0 { keyPrefix += delim } + keyVal := "" + for i, k := range (strings.Split(yangEntry.Key, " ")) { + if i > 0 { keyVal = keyVal + delim } + val := fmt.Sprint(data.(map[string]interface{})[k]) + if strings.Contains(val, ":") { + val = strings.Split(val, ":")[1] + } + keyVal += val + } + keyPrefix += string(keyVal) + } + } + return keyPrefix +} + +/* Copy redis-db source to destn map */ +func mapCopy(destnMap map[string]map[string]db.Value, srcMap map[string]map[string]db.Value) { + for table, tableData := range srcMap { + _, ok := destnMap[table] + if !ok { + destnMap[table] = make(map[string]db.Value) + } + for rule, ruleData := range tableData { + _, ok = destnMap[table][rule] + if !ok { + destnMap[table][rule] = db.Value{Field: make(map[string]string)} + } + for field, value := range ruleData.Field { + destnMap[table][rule].Field[field] = value + } + } + } +} + +func parentXpathGet(xpath string) string { + path := "" + if len(xpath) > 0 { + p := strings.Split(xpath, "/") + path = strings.Join(p[:len(p)-1], "/") + } + return path +} + +func isYangResType(ytype string) bool { + if ytype == "choose" || ytype == "case" { + return true + } + return false +} + +func yangTypeGet(entry *yang.Entry) string { + if entry != nil && entry.Node != nil { + return entry.Node.Statement().Keyword + } + return "" +} + +func dbKeyToYangDataConvert(uri string, xpath string, dbKey string, dbKeySep string) (map[string]interface{}, string, error) { + var err error + if len(uri) == 0 && len(xpath) == 0 && len(dbKey) == 0 { + err = fmt.Errorf("Insufficient input") + return nil, "", err + } + + if _, ok := xYangSpecMap[xpath]; ok { + if xYangSpecMap[xpath].yangEntry == nil { + err = fmt.Errorf("Yang Entry not available for xpath ", xpath) + return nil, "", nil + } + } + + keyNameList := yangKeyFromEntryGet(xYangSpecMap[xpath].yangEntry) + id := xYangSpecMap[xpath].keyLevel + keyDataList := strings.SplitN(dbKey, dbKeySep, id) + uriWithKey := fmt.Sprintf("%v", xpath) + uriWithKeyCreate := true + if len(keyDataList) == 0 { + keyDataList = append(keyDataList, dbKey) + } + + /* if uri contins key, use it else use xpath */ + if strings.Contains(uri, "[") { + uriXpath, _ := XfmrRemoveXPATHPredicates(uri) + if (uriXpath == xpath && (strings.HasSuffix(uri, "]") || strings.HasSuffix(uri, "]/"))) { + uriWithKeyCreate = false + } + uriWithKey = fmt.Sprintf("%v", uri) + } + + if len(xYangSpecMap[xpath].xfmrKey) > 0 { + var dbs [db.MaxDB]*db.DB + inParams := formXfmrInputRequest(nil, dbs, db.MaxDB, nil, uri, GET, dbKey, nil, nil) + ret, err := XlateFuncCall(dbToYangXfmrFunc(xYangSpecMap[xpath].xfmrKey), inParams) + if err != nil { + return nil, "", err + } + rmap := ret[0].Interface().(map[string]interface{}) + if uriWithKeyCreate { + for k, v := range rmap { + uriWithKey += fmt.Sprintf("[%v=%v]", k, v) + } + } + return rmap, uriWithKey, nil + } + + if len(keyNameList) == 0 { + return nil, "", nil + } + + + rmap := make(map[string]interface{}) + if len(keyNameList) > 1 { + log.Infof("No key transformer found for multi element yang key mapping to a single redis key string.") + return rmap, uriWithKey, nil + } + rmap[keyNameList[0]] = keyDataList[0] + if uriWithKeyCreate { + uriWithKey += fmt.Sprintf("[%v=%v]", keyNameList[0], keyDataList[0]) + } + + return rmap, uriWithKey, nil +} + +func contains(sl []string, str string) bool { + for _, v := range sl { + if v == str { + return true + } + } + return false +} + +func isSubtreeRequest(targetUriPath string, nodePath string) bool { + if len(targetUriPath) > 0 && len(nodePath) > 0 { + return strings.HasPrefix(targetUriPath, nodePath) + } + return false +} + +func getYangPathFromUri(uri string) (string, error) { + var path *gnmi.Path + var err error + + path, err = ygot.StringToPath(uri, ygot.StructuredPath, ygot.StringSlicePath) + if err != nil { + log.Errorf("Error in uri to path conversion: %v", err) + return "", err + } + + yangPath, yperr := ygot.PathToSchemaPath(path) + if yperr != nil { + log.Errorf("Error in Gnmi path to Yang path conversion: %v", yperr) + return "", yperr + } + + return yangPath, err +} + +func yangKeyFromEntryGet(entry *yang.Entry) []string { + var keyList []string + for _, key := range strings.Split(entry.Key, " ") { + keyList = append(keyList, key) + } + return keyList +} + +func isSonicYang(path string) bool { + if strings.HasPrefix(path, "/sonic") { + return true + } + return false +} + +func sonicKeyDataAdd(dbIndex db.DBNum, keyNameList []string, keyStr string, resultMap map[string]interface{}) { + var dbOpts db.Options + dbOpts = getDBOptions(dbIndex) + keySeparator := dbOpts.KeySeparator + keyValList := strings.Split(keyStr, keySeparator) + + if len(keyNameList) != len(keyValList) { + return + } + + for i, keyName := range keyNameList { + resultMap[keyName] = keyValList[i] + } +} + +func yangToDbXfmrFunc(funcName string) string { + return ("YangToDb_" + funcName) +} + +func uriWithKeyCreate (uri string, xpathTmplt string, data interface{}) (string, error) { + var err error + if _, ok := xYangSpecMap[xpathTmplt]; ok { + yangEntry := xYangSpecMap[xpathTmplt].yangEntry + if yangEntry != nil { + for _, k := range (strings.Split(yangEntry.Key, " ")) { + uri += fmt.Sprintf("[%v=%v]", k, data.(map[string]interface{})[k]) + } + } else { + err = fmt.Errorf("Yang Entry not available for xpath ", xpathTmplt) + } + } else { + err = fmt.Errorf("No entry in xYangSpecMap for xpath ", xpathTmplt) + } + return uri, err +} + +func xpathRootNameGet(path string) string { + if len(path) > 0 { + pathl := strings.Split(path, "/") + return ("/" + pathl[1]) + } + return "" +} + +func getDbNum(xpath string ) db.DBNum { + _, ok := xYangSpecMap[xpath] + if ok { + xpathInfo := xYangSpecMap[xpath] + return xpathInfo.dbIndex + } + // Default is ConfigDB + return db.ConfigDB +} + +func dbToYangXfmrFunc(funcName string) string { + return ("DbToYang_" + funcName) +} + +func uriModuleNameGet(uri string) (string, error) { + var err error + result := "" + if len(uri) == 0 { + log.Error("Empty uri string supplied") + err = fmt.Errorf("Empty uri string supplied") + return result, err + } + urislice := strings.Split(uri, ":") + if len(urislice) == 1 { + log.Errorf("uri string %s does not have module name", uri) + err = fmt.Errorf("uri string does not have module name: ", uri) + return result, err + } + moduleNm := strings.Split(urislice[0], "/") + result = moduleNm[1] + if len(strings.Trim(result, " ")) == 0 { + log.Error("Empty module name") + err = fmt.Errorf("No module name found in uri %s", uri) + } + log.Info("module name = ", result) + return result, err +} + +func recMap(rMap interface{}, name []string, id int, max int) { + if id == max || id < 0 { + return + } + val := name[id] + if reflect.ValueOf(rMap).Kind() == reflect.Map { + data := reflect.ValueOf(rMap) + dMap := data.Interface().(map[string]interface{}) + _, ok := dMap[val] + if ok { + recMap(dMap[val], name, id+1, max) + } else { + dMap[val] = make(map[string]interface{}) + recMap(dMap[val], name, id+1, max) + } + } + return +} + +func mapCreate(xpath string) map[string]interface{} { + retMap := make(map[string]interface{}) + if len(xpath) > 0 { + attrList := strings.Split(xpath, "/") + alLen := len(attrList) + recMap(retMap, attrList, 1, alLen) + } + return retMap +} + +func mapInstGet(name []string, id int, max int, inMap interface{}) map[string]interface{} { + if inMap == nil { + return nil + } + result := reflect.ValueOf(inMap).Interface().(map[string]interface{}) + if id == max { + return result + } + val := name[id] + if reflect.ValueOf(inMap).Kind() == reflect.Map { + data := reflect.ValueOf(inMap) + dMap := data.Interface().(map[string]interface{}) + _, ok := dMap[val] + if ok { + result = mapInstGet(name, id+1, max, dMap[val]) + } else { + return result + } + } + return result +} + +func mapGet(xpath string, inMap map[string]interface{}) map[string]interface{} { + attrList := strings.Split(xpath, "/") + alLen := len(attrList) + recMap(inMap, attrList, 1, alLen) + retMap := mapInstGet(attrList, 1, alLen, inMap) + return retMap +} + +func formXfmrInputRequest(d *db.DB, dbs [db.MaxDB]*db.DB, cdb db.DBNum, ygRoot *ygot.GoStruct, uri string, oper int, key string, dbDataMap *map[db.DBNum]map[string]map[string]db.Value, param interface{}) XfmrParams { + var inParams XfmrParams + inParams.d = d + inParams.dbs = dbs + inParams.curDb = cdb + inParams.ygRoot = ygRoot + inParams.uri = uri + inParams.oper = oper + inParams.key = key + inParams.dbDataMap = dbDataMap + inParams.param = param // generic param + + return inParams +} + +func findByValue(m map[string]string, value string) string { + for key, val := range m { + if val == value { + return key + } + } + return "" +} +func findByKey(m map[string]string, key string) string { + if val, found := m[key]; found { + return val + } + return "" +} +func findInMap(m map[string]string, str string) string { + // Check if str exists as a value in map m. + if val := findByKey(m, str); val != "" { + return val + } + + // Check if str exists as a key in map m. + if val := findByValue(m, str); val != "" { + return val + } + + // str doesn't exist in map m. + return "" +} + +func getDBOptions(dbNo db.DBNum) db.Options { + var opt db.Options + + switch dbNo { + case db.ApplDB, db.CountersDB: + opt = getDBOptionsWithSeparator(dbNo, "", ":", ":") + break + case db.FlexCounterDB, db.AsicDB, db.LogLevelDB, db.ConfigDB, db.StateDB: + opt = getDBOptionsWithSeparator(dbNo, "", "|", "|") + break + } + + return opt +} + +func getDBOptionsWithSeparator(dbNo db.DBNum, initIndicator string, tableSeparator string, keySeparator string) db.Options { + return(db.Options { + DBNo : dbNo, + InitIndicator : initIndicator, + TableNameSeparator: tableSeparator, + KeySeparator : keySeparator, + }) +} + +func stripAugmentedModuleNames(xpath string) string { + pathList := strings.Split(xpath, "/") + pathList = pathList[1:] + for i, pvar := range pathList { + if (i > 0) && strings.Contains(pvar, ":") { + pvar = strings.Split(pvar,":")[1] + pathList[i] = pvar + } + } + path := "/" + strings.Join(pathList, "/") + return path +} + +func XfmrRemoveXPATHPredicates(xpath string) (string, error) { + pathList := strings.Split(xpath, "/") + pathList = pathList[1:] + for i, pvar := range pathList { + if strings.Contains(pvar, "[") && strings.Contains(pvar, "]") { + si, ei := strings.Index(pvar, "["), strings.Index(pvar, "]") + // substring contains [] entries + if (si < ei) { + pvar = strings.Split(pvar, "[")[0] + pathList[i] = pvar + + } else { + // This substring contained a ] before a [. + return "", fmt.Errorf("Incorrect ordering of [] within substring %s of %s, [ pos: %d, ] pos: %d", pvar, xpath, si, ei) + } + } else if strings.Contains(pvar, "[") || strings.Contains(pvar, "]") { + // This substring contained a mismatched pair of []s. + return "", fmt.Errorf("Mismatched brackets within substring %s of %s", pvar, xpath) + } + if (i > 0) && strings.Contains(pvar, ":") { + pvar = strings.Split(pvar,":")[1] + pathList[i] = pvar + } + } + path := "/" + strings.Join(pathList, "/") + return path,nil +} + + /* Extract key vars, create db key and xpath */ + func xpathKeyExtract(d *db.DB, ygRoot *ygot.GoStruct, oper int, path string) (string, string, string) { + keyStr := "" + tableName := "" + pfxPath := "" + rgp := regexp.MustCompile(`\[([^\[\]]*)\]`) + curPathWithKey := "" + cdb := db.ConfigDB + var dbs [db.MaxDB]*db.DB + + pfxPath, _ = XfmrRemoveXPATHPredicates(path) + xpathInfo, ok := xYangSpecMap[pfxPath] + if !ok { + log.Errorf("No entry found in xYangSpecMap for xpath %v.", pfxPath) + return pfxPath, keyStr, tableName + } + cdb = xpathInfo.dbIndex + dbOpts := getDBOptions(cdb) + keySeparator := dbOpts.KeySeparator + if len(xpathInfo.delim) > 0 { + keySeparator = xpathInfo.delim + } + + for _, k := range strings.Split(path, "/") { + curPathWithKey += k + yangXpath, _ := XfmrRemoveXPATHPredicates(curPathWithKey) + _, ok := xYangSpecMap[yangXpath] + if ok { + if strings.Contains(k, "[") { + if len(keyStr) > 0 { + keyStr += keySeparator + } + if len(xYangSpecMap[yangXpath].xfmrKey) > 0 { + xfmrFuncName := yangToDbXfmrFunc(xYangSpecMap[yangXpath].xfmrKey) + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, curPathWithKey, oper, "", nil, nil) + ret, err := XlateFuncCall(xfmrFuncName, inParams) + if err != nil { + return "", "", "" + } + if ret != nil { + keyStr = ret[0].Interface().(string) + } + } else if xYangSpecMap[yangXpath].keyName != nil { + keyStr += *xYangSpecMap[yangXpath].keyName + } else { + /* multi-leaf yang key together forms a single key-string in redis. + There should be key-transformer, if not then the yang key leaves + will be concatenated with respective default DB type key-delimiter + */ + for idx, kname := range rgp.FindAllString(k, -1) { + if idx > 0 { keyStr += keySeparator } + keyl := strings.TrimRight(strings.TrimLeft(kname, "["), "]") + if strings.Contains(keyl, ":") { + keyl = strings.Split(keyl, ":")[1] + } + keyStr += strings.Split(keyl, "=")[1] + } + } + } else if len(xYangSpecMap[yangXpath].xfmrKey) > 0 { + xfmrFuncName := yangToDbXfmrFunc(xYangSpecMap[yangXpath].xfmrKey) + inParams := formXfmrInputRequest(d, dbs, db.MaxDB, ygRoot, curPathWithKey, oper, "", nil, nil) + ret, err := XlateFuncCall(xfmrFuncName, inParams) + if err != nil { + return "", "", "" + } + if ret != nil { + keyStr = ret[0].Interface().(string) + } + } else if xYangSpecMap[yangXpath].keyName != nil { + keyStr += *xYangSpecMap[yangXpath].keyName + } + } + curPathWithKey += "/" + } + curPathWithKey = strings.TrimSuffix(curPathWithKey, "/") + tblPtr := xpathInfo.tableName + if tblPtr != nil { + tableName = *tblPtr + } else if xpathInfo.xfmrTbl != nil { + inParams := formXfmrInputRequest(d, dbs, cdb, ygRoot, curPathWithKey, oper, "", nil, nil) + tableName, _ = tblNameFromTblXfmrGet(*xpathInfo.xfmrTbl, inParams) + } + return pfxPath, keyStr, tableName + } + + func sonicXpathKeyExtract(path string) (string, string, string) { + xpath, keyStr, tableName := "", "", "" + var err error + xpath, err = XfmrRemoveXPATHPredicates(path) + if err != nil { + return xpath, keyStr, tableName + } + rgp := regexp.MustCompile(`\[([^\[\]]*)\]`) + pathsubStr := strings.Split(path , "/") + if len(pathsubStr) > SONIC_TABLE_INDEX { + if strings.Contains(pathsubStr[2], "[") { + tableName = strings.Split(pathsubStr[SONIC_TABLE_INDEX], "[")[0] + } else { + tableName = pathsubStr[SONIC_TABLE_INDEX] + } + dbInfo, ok := xDbSpecMap[tableName] + cdb := db.ConfigDB + if !ok { + log.Infof("No entry in xDbSpecMap for xpath %v in order to fetch DB index.", tableName) + return xpath, keyStr, tableName + } + cdb = dbInfo.dbIndex + dbOpts := getDBOptions(cdb) + if dbInfo.keyName != nil { + keyStr = *dbInfo.keyName + } else { + for i, kname := range rgp.FindAllString(path, -1) { + if i > 0 { + keyStr += dbOpts.KeySeparator + } + val := strings.Split(kname, "=")[1] + keyStr += strings.TrimRight(val, "]") + } + } + } + return xpath, keyStr, tableName + } + diff --git a/translib/transformer/xspec.go b/translib/transformer/xspec.go new file mode 100644 index 000000000..9a6951fee --- /dev/null +++ b/translib/transformer/xspec.go @@ -0,0 +1,598 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "fmt" + "os" + "strings" + log "github.com/golang/glog" + "github.com/Azure/sonic-mgmt-common/translib/db" + + "github.com/openconfig/goyang/pkg/yang" +) + +/* Data needed to construct lookup table from yang */ +type yangXpathInfo struct { + yangDataType string + tableName *string + xfmrTbl *string + childTable []string + dbEntry *yang.Entry + yangEntry *yang.Entry + keyXpath map[int]*[]string + delim string + fieldName string + xfmrFunc string + xfmrPost string + validateFunc string + xfmrKey string + keyName *string + dbIndex db.DBNum + keyLevel int + isKey bool +} + +type dbInfo struct { + dbIndex db.DBNum + keyName *string + fieldType string + dbEntry *yang.Entry + yangXpath []string + module string +} + +var xYangSpecMap map[string]*yangXpathInfo +var xDbSpecMap map[string]*dbInfo +var xDbSpecOrdTblMap map[string][]string //map of module-name to ordered list of db tables { "sonic-acl" : ["ACL_TABLE", "ACL_RULE"] } + +/* update transformer spec with db-node */ +func updateDbTableData (xpath string, xpathData *yangXpathInfo, tableName string) { + _, ok := xDbSpecMap[tableName] + if ok { + xDbSpecMap[tableName].yangXpath = append(xDbSpecMap[tableName].yangXpath, xpath) + xpathData.dbEntry = xDbSpecMap[tableName].dbEntry + } +} + +/* Recursive api to fill the map with yang details */ +func yangToDbMapFill (keyLevel int, xYangSpecMap map[string]*yangXpathInfo, entry *yang.Entry, xpathPrefix string) { + xpath := "" + /* create the yang xpath */ + if xYangSpecMap[xpathPrefix] != nil && xYangSpecMap[xpathPrefix].yangDataType == "module" { + /* module name is separated from the rest of xpath with ":" */ + xpath = xpathPrefix + ":" + entry.Name + } else { + xpath = xpathPrefix + "/" + entry.Name + } + + xpathData, ok := xYangSpecMap[xpath] + if !ok { + xpathData = new(yangXpathInfo) + xYangSpecMap[xpath] = xpathData + xpathData.dbIndex = db.ConfigDB // default value + } else { + xpathData = xYangSpecMap[xpath] + } + + xpathData.yangDataType = entry.Node.Statement().Keyword + if entry.Node.Statement().Keyword == "list" && xpathData.tableName != nil { + childToUpdateParent(xpath, *xpathData.tableName) + } + + parentXpathData, ok := xYangSpecMap[xpathPrefix] + /* init current xpath table data with its parent data, change only if needed. */ + if ok { + if xpathData.tableName == nil && parentXpathData.tableName != nil && xpathData.xfmrTbl == nil { + xpathData.tableName = parentXpathData.tableName + } else if xpathData.xfmrTbl == nil && parentXpathData.xfmrTbl != nil { + xpathData.xfmrTbl = parentXpathData.xfmrTbl + } + } + + if ok && xpathData.dbIndex == db.ConfigDB && parentXpathData.dbIndex != db.ConfigDB { + // If DB Index is not annotated and parent DB index is annotated inherit the DB Index of the parent + xpathData.dbIndex = parentXpathData.dbIndex + } + + if ok && len(parentXpathData.validateFunc) > 0 { + xpathData.validateFunc = parentXpathData.validateFunc + } + + if ok && len(parentXpathData.xfmrFunc) > 0 && len(xpathData.xfmrFunc) == 0 { + xpathData.xfmrFunc = parentXpathData.xfmrFunc + } + + if xpathData.yangDataType == "leaf" && len(xpathData.fieldName) == 0 { + if xpathData.tableName != nil && xDbSpecMap[*xpathData.tableName] != nil { + if xDbSpecMap[*xpathData.tableName].dbEntry.Dir[entry.Name] != nil { + xpathData.fieldName = entry.Name + } else if xDbSpecMap[*xpathData.tableName].dbEntry.Dir[strings.ToUpper(entry.Name)] != nil { + xpathData.fieldName = strings.ToUpper(entry.Name) + } + } else if xpathData.xfmrTbl != nil { + /* table transformer present */ + xpathData.fieldName = entry.Name + } + } + + if xpathData.yangDataType == "leaf" && len(xpathData.fieldName) > 0 && xpathData.tableName != nil { + dbPath := *xpathData.tableName + "/" + xpathData.fieldName + if xDbSpecMap[dbPath] != nil { + xDbSpecMap[dbPath].yangXpath = append(xDbSpecMap[dbPath].yangXpath, xpath) + } + } + + /* fill table with key data. */ + curKeyLevel := keyLevel + if len(entry.Key) != 0 { + parentKeyLen := 0 + + /* create list with current keys */ + keyXpath := make([]string, len(strings.Split(entry.Key, " "))) + for id, keyName := range(strings.Split(entry.Key, " ")) { + keyXpath[id] = xpath + "/" + keyName + keyXpathData := new(yangXpathInfo) + xYangSpecMap[xpath + "/" + keyName] = keyXpathData + xYangSpecMap[xpath + "/" + keyName].isKey = true + } + + xpathData.keyXpath = make(map[int]*[]string, (parentKeyLen + 1)) + k := 0 + for ; k < parentKeyLen; k++ { + /* copy parent key-list to child key-list*/ + xpathData.keyXpath[k] = parentXpathData.keyXpath[k] + } + xpathData.keyXpath[k] = &keyXpath + xpathData.keyLevel = curKeyLevel + curKeyLevel++ + } else if parentXpathData != nil && parentXpathData.keyXpath != nil { + xpathData.keyXpath = parentXpathData.keyXpath + } + + /* get current obj's children */ + var childList []string + for k := range entry.Dir { + childList = append(childList, k) + } + + xpathData.yangEntry = entry + /* now recurse, filling the map with current node's children info */ + for _, child := range childList { + yangToDbMapFill(curKeyLevel, xYangSpecMap, entry.Dir[child], xpath) + } +} + +/* Build lookup table based of yang xpath */ +func yangToDbMapBuild(entries map[string]*yang.Entry) { + if entries == nil { + return + } + + if xYangSpecMap == nil { + xYangSpecMap = make(map[string]*yangXpathInfo) + } + + for module, e := range entries { + if e == nil || len(e.Dir) == 0 { + continue + } + + /* Start to fill xpath based map with yang data */ + keyLevel := 0 + yangToDbMapFill(keyLevel, xYangSpecMap, e, "") + + // Fill the ordered map of child tables list for oc yangs + updateSchemaOrderedMap(module, e) + } + mapPrint(xYangSpecMap, "/tmp/fullSpec.txt") + dbMapPrint("/tmp/dbSpecMap.txt") +} + +/* Fill the map with db details */ +func dbMapFill(tableName string, curPath string, moduleNm string, trkTpCnt bool, xDbSpecMap map[string]*dbInfo, entry *yang.Entry) { + entryType := entry.Node.Statement().Keyword + + if entry.Name != moduleNm { + if entryType == "container" { + tableName = entry.Name + } + + if !isYangResType(entryType) { + dbXpath := tableName + if entryType != "container" { + dbXpath = tableName + "/" + entry.Name + } + xDbSpecMap[dbXpath] = new(dbInfo) + xDbSpecMap[dbXpath].dbIndex = db.MaxDB + xDbSpecMap[dbXpath].dbEntry = entry + xDbSpecMap[dbXpath].fieldType = entryType + xDbSpecMap[dbXpath].module = moduleNm + if entryType == "container" { + xDbSpecMap[dbXpath].dbIndex = db.ConfigDB + if entry.Exts != nil && len(entry.Exts) > 0 { + for _, ext := range entry.Exts { + dataTagArr := strings.Split(ext.Keyword, ":") + tagType := dataTagArr[len(dataTagArr)-1] + switch tagType { + case "key-name" : + if xDbSpecMap[dbXpath].keyName == nil { + xDbSpecMap[dbXpath].keyName = new(string) + } + *xDbSpecMap[dbXpath].keyName = ext.NName() + default : + log.Infof("Unsupported ext type(%v) for xpath(%v).", tagType, dbXpath) + } + } + } + } + } + } else { + moduleXpath := "/" + moduleNm + ":" + entry.Name + xDbSpecMap[moduleXpath] = new(dbInfo) + xDbSpecMap[moduleXpath].dbEntry = entry + xDbSpecMap[moduleXpath].fieldType = entryType + xDbSpecMap[moduleXpath].module = moduleNm + } + + var childList []string + for _, k := range entry.DirOKeys { + childList = append(childList, k) + } + + if entryType == "container" && trkTpCnt { + xDbSpecOrdTblMap[moduleNm] = childList + log.Info("xDbSpecOrdTblMap after appending ", xDbSpecOrdTblMap) + trkTpCnt = false + } + + for _, child := range childList { + childPath := tableName + "/" + entry.Dir[child].Name + dbMapFill(tableName, childPath, moduleNm, trkTpCnt, xDbSpecMap, entry.Dir[child]) + } +} + +/* Build redis db lookup map */ +func dbMapBuild(entries []*yang.Entry) { + if entries == nil { + return + } + xDbSpecMap = make(map[string]*dbInfo) + xDbSpecOrdTblMap = make(map[string][]string) + + for _, e := range entries { + if e == nil || len(e.Dir) == 0 { + continue + } + moduleNm := e.Name + log.Infof("Module name(%v)", moduleNm) + trkTpCnt := true + dbMapFill("", "", moduleNm, trkTpCnt, xDbSpecMap, e) + } +} + +func childToUpdateParent( xpath string, tableName string) { + var xpathData *yangXpathInfo + parent := parentXpathGet(xpath) + if len(parent) == 0 || parent == "/" { + return + } + + _, ok := xYangSpecMap[parent] + if !ok { + xpathData = new(yangXpathInfo) + xYangSpecMap[parent] = xpathData + } + xYangSpecMap[parent].childTable = append(xYangSpecMap[parent].childTable, tableName) + if xYangSpecMap[parent].yangEntry != nil && + xYangSpecMap[parent].yangEntry.Node.Statement().Keyword == "list" { + return + } + childToUpdateParent(parent, tableName) +} + +/* Build lookup map based on yang xpath */ +func annotEntryFill(xYangSpecMap map[string]*yangXpathInfo, xpath string, entry *yang.Entry) { + xpathData := new(yangXpathInfo) + _, ok := xYangSpecMap[xpath] + if !ok { + fmt.Printf("Xpath not found(%v) \r\n", xpath) + } + + xpathData.dbIndex = db.ConfigDB // default value + /* fill table with yang extension data. */ + if entry != nil && len(entry.Exts) > 0 { + for _, ext := range entry.Exts { + dataTagArr := strings.Split(ext.Keyword, ":") + tagType := dataTagArr[len(dataTagArr)-1] + switch tagType { + case "table-name" : + if xpathData.tableName == nil { + xpathData.tableName = new(string) + } + *xpathData.tableName = ext.NName() + updateDbTableData(xpath, xpathData, *xpathData.tableName) + case "key-name" : + if xpathData.keyName == nil { + xpathData.keyName = new(string) + } + *xpathData.keyName = ext.NName() + case "table-transformer" : + if xpathData.xfmrTbl == nil { + xpathData.xfmrTbl = new(string) + } + *xpathData.xfmrTbl = ext.NName() + case "field-name" : + xpathData.fieldName = ext.NName() + case "subtree-transformer" : + xpathData.xfmrFunc = ext.NName() + case "key-transformer" : + xpathData.xfmrKey = ext.NName() + case "key-delimiter" : + xpathData.delim = ext.NName() + case "field-transformer" : + xpathData.xfmrFunc = ext.NName() + case "post-transformer" : + xpathData.xfmrPost = ext.NName() + case "get-validate" : + xpathData.validateFunc = ext.NName() + case "use-self-key" : + xpathData.keyXpath = nil + case "db-name" : + if ext.NName() == "APPL_DB" { + xpathData.dbIndex = db.ApplDB + } else if ext.NName() == "ASIC_DB" { + xpathData.dbIndex = db.AsicDB + } else if ext.NName() == "COUNTERS_DB" { + xpathData.dbIndex = db.CountersDB + } else if ext.NName() == "LOGLEVEL_DB" { + xpathData.dbIndex = db.LogLevelDB + } else if ext.NName() == "CONFIG_DB" { + xpathData.dbIndex = db.ConfigDB + } else if ext.NName() == "FLEX_COUNTER_DB" { + xpathData.dbIndex = db.FlexCounterDB + } else if ext.NName() == "STATE_DB" { + xpathData.dbIndex = db.StateDB + } else { + xpathData.dbIndex = db.ConfigDB + } + } + } + } + xYangSpecMap[xpath] = xpathData +} + +/* Build xpath from yang-annotation */ +func xpathFromDevCreate(path string) string { + p := strings.Split(path, "/") + for i, k := range p { + if len(k) > 0 { p[i] = strings.Split(k, ":")[1] } + } + return strings.Join(p[1:], "/") +} + +/* Build lookup map based on yang xpath */ +func annotToDbMapBuild(annotEntries []*yang.Entry) { + if annotEntries == nil { + return + } + if xYangSpecMap == nil { + xYangSpecMap = make(map[string]*yangXpathInfo) + } + + for _, e := range annotEntries { + if e != nil && len(e.Deviations) > 0 { + for _, d := range e.Deviations { + xpath := xpathFromDevCreate(d.Name) + xpath = "/" + strings.Replace(e.Name, "-annot", "", -1) + ":" + xpath + for i, deviate := range d.Deviate { + if i == 2 { + for _, ye := range deviate { + annotEntryFill(xYangSpecMap, xpath, ye) + } + } + } + } + } + } + mapPrint(xYangSpecMap, "/tmp/annotSpec.txt") +} + +func annotDbSpecMapFill(xDbSpecMap map[string]*dbInfo, dbXpath string, entry *yang.Entry) error { + var err error + var dbXpathData *dbInfo + var ok bool + + //Currently sonic-yang annotation is supported for "list" type only. + listName := strings.Split(dbXpath, "/") + if len(listName) < 3 { + log.Errorf("Invalid list xpath length(%v) \r\n", dbXpath) + return err + } + dbXpathData, ok = xDbSpecMap[listName[2]] + if !ok { + log.Errorf("DB spec-map data not found(%v) \r\n", dbXpath) + return err + } + log.Infof("Annotate dbSpecMap for (%v)(listName:%v)\r\n", dbXpath, listName[2]) + dbXpathData.dbIndex = db.ConfigDB // default value + + /* fill table with cvl yang extension data. */ + if entry != nil && len(entry.Exts) > 0 { + for _, ext := range entry.Exts { + dataTagArr := strings.Split(ext.Keyword, ":") + tagType := dataTagArr[len(dataTagArr)-1] + switch tagType { + case "key-name" : + if dbXpathData.keyName == nil { + dbXpathData.keyName = new(string) + } + *dbXpathData.keyName = ext.NName() + case "db-name" : + if ext.NName() == "APPL_DB" { + dbXpathData.dbIndex = db.ApplDB + } else if ext.NName() == "ASIC_DB" { + dbXpathData.dbIndex = db.AsicDB + } else if ext.NName() == "COUNTERS_DB" { + dbXpathData.dbIndex = db.CountersDB + } else if ext.NName() == "LOGLEVEL_DB" { + dbXpathData.dbIndex = db.LogLevelDB + } else if ext.NName() == "CONFIG_DB" { + dbXpathData.dbIndex = db.ConfigDB + } else if ext.NName() == "FLEX_COUNTER_DB" { + dbXpathData.dbIndex = db.FlexCounterDB + } else if ext.NName() == "STATE_DB" { + dbXpathData.dbIndex = db.StateDB + } else { + dbXpathData.dbIndex = db.ConfigDB + } + default : + } + } + } + + dbMapPrint("/tmp/dbSpecMapFull.txt") + return err +} + +func annotDbSpecMap(annotEntries []*yang.Entry) { + if annotEntries == nil || xDbSpecMap == nil { + return + } + for _, e := range annotEntries { + if e != nil && len(e.Deviations) > 0 { + for _, d := range e.Deviations { + xpath := xpathFromDevCreate(d.Name) + xpath = "/" + strings.Replace(e.Name, "-annot", "", -1) + ":" + xpath + for i, deviate := range d.Deviate { + if i == 2 { + for _, ye := range deviate { + annotDbSpecMapFill(xDbSpecMap, xpath, ye) + } + } + } + } + } + } +} + +/* Debug function to print the yang xpath lookup map */ +func mapPrint(inMap map[string]*yangXpathInfo, fileName string) { + fp, err := os.Create(fileName) + if err != nil { + return + } + defer fp.Close() + + for k, d := range inMap { + fmt.Fprintf (fp, "-----------------------------------------------------------------\r\n") + fmt.Fprintf(fp, "%v:\r\n", k) + fmt.Fprintf(fp, " yangDataType: %v\r\n", d.yangDataType) + fmt.Fprintf(fp, " tableName: ") + if d.tableName != nil { + fmt.Fprintf(fp, "%v", *d.tableName) + } + fmt.Fprintf(fp, "\r\n xfmrTbl : ") + if d.xfmrTbl != nil { + fmt.Fprintf(fp, "%v", *d.xfmrTbl) + } + fmt.Fprintf(fp, "\r\n keyName : ") + if d.keyName != nil { + fmt.Fprintf(fp, "%v", *d.keyName) + } + fmt.Fprintf(fp, "\r\n childTbl : %v", d.childTable) + fmt.Fprintf(fp, "\r\n FieldName: %v", d.fieldName) + fmt.Fprintf(fp, "\r\n keyLevel : %v", d.keyLevel) + fmt.Fprintf(fp, "\r\n xfmrKeyFn: %v", d.xfmrKey) + fmt.Fprintf(fp, "\r\n xfmrFunc : %v", d.xfmrFunc) + fmt.Fprintf(fp, "\r\n dbIndex : %v", d.dbIndex) + fmt.Fprintf(fp, "\r\n validateFunc : %v", d.validateFunc) + fmt.Fprintf(fp, "\r\n yangEntry: ") + if d.yangEntry != nil { + fmt.Fprintf(fp, "%v", *d.yangEntry) + } + fmt.Fprintf(fp, "\r\n dbEntry: ") + if d.dbEntry != nil { + fmt.Fprintf(fp, "%v", *d.dbEntry) + } + fmt.Fprintf(fp, "\r\n keyXpath: %d\r\n", d.keyXpath) + for i, kd := range d.keyXpath { + fmt.Fprintf(fp, " %d. %#v\r\n", i, kd) + } + fmt.Fprintf(fp, "\r\n isKey : %v\r\n", d.isKey) + } + fmt.Fprintf (fp, "-----------------------------------------------------------------\r\n") + +} + +/* Debug function to print redis db lookup map */ +func dbMapPrint( fname string) { + fp, err := os.Create(fname) + if err != nil { + return + } + defer fp.Close() + fmt.Fprintf (fp, "-----------------------------------------------------------------\r\n") + for k, v := range xDbSpecMap { + fmt.Fprintf(fp, " field:%v \r\n", k) + fmt.Fprintf(fp, " type :%v \r\n", v.fieldType) + fmt.Fprintf(fp, " db-type :%v \r\n", v.dbIndex) + fmt.Fprintf(fp, " module :%v \r\n", v.module) + fmt.Fprintf(fp, " KeyName: ") + if v.keyName != nil { + fmt.Fprintf(fp, "%v", *v.keyName) + } + fmt.Fprintf(fp, "\r\n oc-yang :%v \r\n", v.yangXpath) + fmt.Fprintf(fp, " cvl-yang :%v \r\n", v.dbEntry) + fmt.Fprintf (fp, "-----------------------------------------------------------------\r\n") + + } +} + +func updateSchemaOrderedMap(module string, entry *yang.Entry) { + var children []string + if entry.Node.Statement().Keyword == "module" { + for _, dir := range entry.DirOKeys { + // Gives the yang xpath for the top level container + xpath := "/" + module + ":" + dir + _, ok := xYangSpecMap[xpath] + if ok { + yentry := xYangSpecMap[xpath].yangEntry + if yentry.Node.Statement().Keyword == "container" { + var keyspec = make([]KeySpec, 0) + keyspec = FillKeySpecs(xpath, "" , &keyspec) + children = updateChildTable(keyspec, &children) + } + } + } + } + xDbSpecOrdTblMap[module] = children +} + +func updateChildTable(keyspec []KeySpec, chlist *[]string) ([]string) { + for _, ks := range keyspec { + if (ks.Ts.Name != "") { + if !contains(*chlist, ks.Ts.Name) { + *chlist = append(*chlist, ks.Ts.Name) + } + } + *chlist = updateChildTable(ks.Child, chlist) + } + return *chlist +} diff --git a/translib/translib.go b/translib/translib.go new file mode 100644 index 000000000..bc68b0234 --- /dev/null +++ b/translib/translib.go @@ -0,0 +1,745 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +/* +Package translib implements APIs like Create, Get, Subscribe etc. + +to be consumed by the north bound management server implementations + +This package take care of translating the incoming requests to + +Redis ABNF format and persisting them in the Redis DB. + +It can also translate the ABNF format to YANG specific JSON IETF format + +This package can also talk to non-DB clients. +*/ + +package translib + +import ( + //"errors" + "sync" + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Workiva/go-datastructures/queue" + log "github.com/golang/glog" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" +) + +//Write lock for all write operations to be synchronized +var writeMutex = &sync.Mutex{} + +//minimum global interval for subscribe in secs +var minSubsInterval = 20 +var maxSubsInterval = 60 + +type ErrSource int + +const( + ProtoErr ErrSource = iota + AppErr +) + +type SetRequest struct{ + Path string + Payload []byte +} + +type SetResponse struct{ + ErrSrc ErrSource +} + +type GetRequest struct{ + Path string +} + +type GetResponse struct{ + Payload []byte + ErrSrc ErrSource +} + +type SubscribeResponse struct{ + Path string + Payload []byte + Timestamp int64 + SyncComplete bool + IsTerminated bool +} + +type NotificationType int + +const( + Sample NotificationType = iota + OnChange +) + +type IsSubscribeResponse struct{ + Path string + IsOnChangeSupported bool + MinInterval int + Err error + PreferredType NotificationType +} + +type ModelData struct{ + Name string + Org string + Ver string +} + +type notificationOpts struct { + mInterval int + pType NotificationType // for TARGET_DEFINED +} + +//initializes logging and app modules +func init() { + log.Flush() +} + +//Creates entries in the redis DB pertaining to the path and payload +func Create(req SetRequest) (SetResponse, error){ + var keys []db.WatchKeys + var resp SetResponse + + path := req.Path + payload := req.Payload + + log.Info("Create request received with path =", path) + log.Info("Create request received with payload =", string(payload)) + + app, appInfo, err := getAppModule(path) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + err = appInitialize(app, appInfo, path, &payload, CREATE) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + writeMutex.Lock() + defer writeMutex.Unlock() + + d, err := db.NewDB(getDBOptions(db.ConfigDB)) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + defer d.DeleteDB() + + keys, err = (*app).translateCreate(d) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + err = d.StartTx(keys, appInfo.tablesToWatch) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + resp, err = (*app).processCreate (d) + + if err != nil { + d.AbortTx() + resp.ErrSrc = AppErr + return resp, err + } + + err = d.CommitTx() + + if err != nil { + resp.ErrSrc = AppErr + } + + return resp, err +} + +//Updates entries in the redis DB pertaining to the path and payload +func Update(req SetRequest) (SetResponse, error){ + var keys []db.WatchKeys + var resp SetResponse + + path := req.Path + payload := req.Payload + + log.Info("Update request received with path =", path) + log.Info("Update request received with payload =", string(payload)) + + app, appInfo, err := getAppModule(path) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + err = appInitialize(app, appInfo, path, &payload, UPDATE) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + writeMutex.Lock() + defer writeMutex.Unlock() + + d, err := db.NewDB(getDBOptions(db.ConfigDB)) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + defer d.DeleteDB() + + keys, err = (*app).translateUpdate(d) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + err = d.StartTx(keys, appInfo.tablesToWatch) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + resp, err = (*app).processUpdate (d) + + if err != nil { + d.AbortTx() + resp.ErrSrc = AppErr + return resp, err + } + + err = d.CommitTx() + + if err != nil { + resp.ErrSrc = AppErr + } + + return resp, err +} + +//Replaces entries in the redis DB pertaining to the path and payload +func Replace(req SetRequest) (SetResponse, error){ + var err error + var keys []db.WatchKeys + var resp SetResponse + + path := req.Path + payload := req.Payload + + log.Info("Replace request received with path =", path) + log.Info("Replace request received with payload =", string(payload)) + + app, appInfo, err := getAppModule(path) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + err = appInitialize(app, appInfo, path, &payload, REPLACE) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + writeMutex.Lock() + defer writeMutex.Unlock() + + d, err := db.NewDB(getDBOptions(db.ConfigDB)) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + defer d.DeleteDB() + + keys, err = (*app).translateReplace(d) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + err = d.StartTx(keys, appInfo.tablesToWatch) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + resp, err = (*app).processReplace (d) + + if err != nil { + d.AbortTx() + resp.ErrSrc = AppErr + return resp, err + } + + err = d.CommitTx() + + if err != nil { + resp.ErrSrc = AppErr + } + + return resp, err +} + +//Deletes entries in the redis DB pertaining to the path +func Delete(req SetRequest) (SetResponse, error){ + var err error + var keys []db.WatchKeys + var resp SetResponse + + path := req.Path + + log.Info("Delete request received with path =", path) + + app, appInfo, err := getAppModule(path) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + err = appInitialize(app, appInfo, path, nil, DELETE) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + writeMutex.Lock() + defer writeMutex.Unlock() + + d, err := db.NewDB(getDBOptions(db.ConfigDB)) + + if err != nil { + resp.ErrSrc = ProtoErr + return resp, err + } + + defer d.DeleteDB() + + keys, err = (*app).translateDelete(d) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + err = d.StartTx(keys, appInfo.tablesToWatch) + + if err != nil { + resp.ErrSrc = AppErr + return resp, err + } + + resp, err = (*app).processDelete(d) + + if err != nil { + d.AbortTx() + resp.ErrSrc = AppErr + return resp, err + } + + err = d.CommitTx() + + if err != nil { + resp.ErrSrc = AppErr + } + + return resp, err +} + +//Gets data from the redis DB and converts it to northbound format +func Get(req GetRequest) (GetResponse, error){ + var payload []byte + var resp GetResponse + + path := req.Path + + log.Info("Received Get request for path = ",path) + + app, appInfo, err := getAppModule(path) + + if err != nil { + resp = GetResponse{Payload:payload, ErrSrc:ProtoErr} + return resp, err + } + + err = appInitialize(app, appInfo, path, nil, GET) + + if err != nil { + resp = GetResponse{Payload:payload, ErrSrc:AppErr} + return resp, err + } + + dbs, err := getAllDbs() + + if err != nil { + resp = GetResponse{Payload:payload, ErrSrc:ProtoErr} + return resp, err + } + + defer closeAllDbs(dbs[:]) + + err = (*app).translateGet (dbs) + + if err != nil { + resp = GetResponse{Payload:payload, ErrSrc:AppErr} + return resp, err + } + + resp, err = (*app).processGet(dbs) + + return resp, err +} + +//Subscribes to the paths requested and sends notifications when the data changes in DB +func Subscribe(paths []string, q *queue.PriorityQueue, stop chan struct{}) ([]*IsSubscribeResponse, error) { + var err error + var sErr error + //err = errors.New("Not implemented") + + dbNotificationMap := make(map[db.DBNum][]*notificationInfo) + + resp := make ([]*IsSubscribeResponse, len(paths)) + + for i, _ := range resp { + resp[i] = &IsSubscribeResponse{Path: paths[i], + IsOnChangeSupported: false, + MinInterval: minSubsInterval, + PreferredType:Sample, + Err:nil} + } + + dbs, err := getAllDbs() + + if err != nil { + return resp, err + } + + //Do NOT close the DBs here as we need to use them during subscribe notification + + for i, path := range paths { + + app, appInfo, err := getAppModule(path) + + if err != nil { + + if sErr == nil { + sErr = err + } + + resp[i].Err = err + continue + } + + nOpts, nInfo, errApp := (*app).translateSubscribe (dbs, path) + + if errApp != nil { + resp[i].Err = errApp + + if sErr == nil { + sErr = errApp + } + + resp[i].MinInterval = maxSubsInterval + + if nOpts != nil { + if nOpts.mInterval != 0 { + resp[i].MinInterval = nOpts.mInterval + } + + resp[i].PreferredType = nOpts.pType + } + + continue + } else { + + if nOpts != nil { + if nOpts.mInterval != 0 { + resp[i].MinInterval = nOpts.mInterval + } + + resp[i].PreferredType = nOpts.pType + } + + if nInfo == nil { + sErr = tlerr.NotSupportedError{ + Format: "Subscribe not supported", Path: path} + resp[i].Err = sErr + continue + } + + resp[i].IsOnChangeSupported = true + + nInfo.path = path + nInfo.app = app + nInfo.appInfo = appInfo + nInfo.dbs = dbs + + dbNotificationMap[nInfo.dbno] = append(dbNotificationMap[nInfo.dbno], nInfo) + } + + } + + log.Info("map=", dbNotificationMap) + + if sErr != nil { + return resp, sErr + } + + sInfo := &subscribeInfo {syncDone:false, + q:q, + stop:stop} + + sErr = startSubscribe(sInfo, dbNotificationMap) + + return resp, sErr +} + +//Check if subscribe is supported on the given paths +func IsSubscribeSupported(paths []string) ([]*IsSubscribeResponse, error) { + + resp := make ([]*IsSubscribeResponse, len(paths)) + + for i, _ := range resp { + resp[i] = &IsSubscribeResponse{Path: paths[i], + IsOnChangeSupported: false, + MinInterval: minSubsInterval, + PreferredType:Sample, + Err:nil} + } + + dbs, err := getAllDbs() + + if err != nil { + return resp, err + } + + defer closeAllDbs(dbs[:]) + + for i, path := range paths { + + app, _, err := getAppModule(path) + + if err != nil { + resp[i].Err = err + continue + } + + nOpts, _, errApp := (*app).translateSubscribe (dbs, path) + + if errApp != nil { + resp[i].Err = errApp + err = errApp + continue + } else { + resp[i].IsOnChangeSupported= true + + if nOpts != nil { + if nOpts.mInterval != 0 { + resp[i].MinInterval = nOpts.mInterval + } + resp[i].PreferredType = nOpts.pType + } + } + } + + return resp, err +} + +//Gets all the models supported by Translib +func GetModels() ([]ModelData, error) { + var err error + + return getModels(), err +} + +//Creates connection will all the redis DBs. To be used for get request +func getAllDbs() ([db.MaxDB]*db.DB, error) { + var dbs [db.MaxDB]*db.DB + var err error + + //Create Application DB connection + dbs[db.ApplDB], err = db.NewDB(getDBOptions(db.ApplDB)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + //Create ASIC DB connection + dbs[db.AsicDB], err = db.NewDB(getDBOptions(db.AsicDB)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + //Create Counter DB connection + dbs[db.CountersDB], err = db.NewDB(getDBOptions(db.CountersDB)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + //Create Log Level DB connection + dbs[db.LogLevelDB], err = db.NewDB(getDBOptions(db.LogLevelDB)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + //Create Config DB connection + dbs[db.ConfigDB], err = db.NewDB(getDBOptions(db.ConfigDB)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + //Create Flex Counter DB connection + dbs[db.FlexCounterDB], err = db.NewDB(getDBOptions(db.FlexCounterDB)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + //Create State DB connection + dbs[db.StateDB], err = db.NewDB(getDBOptions(db.StateDB)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + + return dbs, err +} + +//Closes the dbs, and nils out the arr. +func closeAllDbs(dbs []*db.DB) { + for dbsi, d := range dbs { + if d != nil { + d.DeleteDB() + dbs[dbsi] = nil + } + } +} + +// Implement Compare method for priority queue for SubscribeResponse struct +func (val SubscribeResponse) Compare(other queue.Item) int { + o := other.(*SubscribeResponse) + if val.Timestamp > o.Timestamp { + return 1 + } else if val.Timestamp == o.Timestamp { + return 0 + } + return -1 +} + +func getDBOptions(dbNo db.DBNum) db.Options { + var opt db.Options + + switch dbNo { + case db.ApplDB, db.CountersDB: + opt = getDBOptionsWithSeparator(dbNo, "", ":", ":") + break + case db.FlexCounterDB, db.AsicDB, db.LogLevelDB, db.ConfigDB, db.StateDB: + opt = getDBOptionsWithSeparator(dbNo, "", "|", "|") + break + } + + return opt +} + +func getDBOptionsWithSeparator(dbNo db.DBNum, initIndicator string, tableSeparator string, keySeparator string) db.Options { + return(db.Options { + DBNo : dbNo, + InitIndicator : initIndicator, + TableNameSeparator: tableSeparator, + KeySeparator : keySeparator, + }) +} + +func getAppModule (path string) (*appInterface, *appInfo, error) { + var app appInterface + + aInfo, err := getAppModuleInfo(path) + + if err != nil { + return nil, aInfo, err + } + + app, err = getAppInterface(aInfo.appType) + + if err != nil { + return nil, aInfo, err + } + + return &app, aInfo, err +} + +func appInitialize (app *appInterface, appInfo *appInfo, path string, payload *[]byte, opCode int) error { + var err error + var input []byte + + if payload != nil { + input = *payload + } + + if appInfo.isNative { + log.Info("Native MSFT format") + data := appData{path: path, payload: input} + (*app).initialize(data) + } else { + ygotStruct, ygotTarget, err := getRequestBinder (&path, payload, opCode, &(appInfo.ygotRootType)).unMarshall() + if err != nil { + log.Info("Error in request binding: ", err) + return err + } + + data := appData{path: path, payload: input, ygotRoot: ygotStruct, ygotTarget: ygotTarget} + (*app).initialize(data) + } + + return err +} diff --git a/translib/translib_test.go b/translib/translib_test.go new file mode 100644 index 000000000..9ceb1e1a8 --- /dev/null +++ b/translib/translib_test.go @@ -0,0 +1,59 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or // +// its subsidiaries. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package translib_test + +import ( + "fmt" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +// assert fails the test if the condition is false. +func assert(tb testing.TB, condition bool, msg string, v ...interface{}) { + if !condition { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...) + tb.FailNow() + } +} + +// ok fails the test if an err is not nil. +func ok(tb testing.TB, err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) + tb.FailNow() + } +} + +// equals fails the test if exp is not equal to act. +func equals(tb testing.TB, exp, act interface{}) { + if !reflect.DeepEqual(exp, act) { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) + tb.FailNow() + } +} + +func Test_Create(t *testing.T) { + +} From 361b179b15b1b9e497a751eb8fc8f04be2c2f3ce Mon Sep 17 00:00:00 2001 From: Sachin Holla Date: Tue, 9 Jun 2020 19:18:42 -0700 Subject: [PATCH 2/4] Fix compilation and packaging issue with yangs --- debian/sonic-mgmt-common.install | 3 +-- models/yang/.gitignore | 1 + models/yang/Makefile | 22 ++++------------------ 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/debian/sonic-mgmt-common.install b/debian/sonic-mgmt-common.install index 56647a0c5..b9bdf7ff3 100644 --- a/debian/sonic-mgmt-common.install +++ b/debian/sonic-mgmt-common.install @@ -1,12 +1,11 @@ # Yang models models/yang/*.yang usr/models/yang models/yang/common/*.yang usr/models/yang -models/yang/extensions/*.yang usr/models/yang +#models/yang/extensions/*.yang usr/models/yang models/yang/sonic/*.yang usr/models/yang models/yang/sonic/common/*.yang usr/models/yang models/yang/annotations/*.yang usr/models/yang config/transformer/models_list usr/models/yang -build/yang/api_ignore usr/models/yang # CVL files build/cvl/schema usr/sbin diff --git a/models/yang/.gitignore b/models/yang/.gitignore index 16e3dfc50..2720211d2 100644 --- a/models/yang/.gitignore +++ b/models/yang/.gitignore @@ -1,2 +1,3 @@ *tree.html *.tree +.annots_checked diff --git a/models/yang/Makefile b/models/yang/Makefile index 54f9c6dd5..87c07b435 100644 --- a/models/yang/Makefile +++ b/models/yang/Makefile @@ -31,13 +31,10 @@ YANG_ANNOTATION_FILES := $(wildcard $(YANGDIR_ANNOTATIONS)/*.yang) YANGDIR_SONIC := $(YANGDIR)/sonic YANGDIR_SONIC_COMMON := $(YANGDIR_SONIC)/common -SONIC_YANG_MOD_FILES := $(wildcard *.yang) +SONIC_YANG_MOD_FILES := $(wildcard $(YANGDIR_SONIC)/*.yang) SONIC_YANG_COMMON_FILES := $(wildcard $(YANGDIR_SONIC_COMMON)/*.yang) - -YANG_BUILD_DIR := $(BUILD_DIR)/yang -YANG_API_IGNORES := $(YANG_BUILD_DIR)/api_ignore -YANG_ANNOTS_CHK := $(YANG_BUILD_DIR)/annots_checked +YANG_ANNOTS_CHK := .annots_checked TOOLS_DIR := $(TOPDIR)/tools PYANG_PLUGIN_DIR := $(TOOLS_DIR)/pyang/pyang_plugins @@ -45,14 +42,10 @@ PYANG ?= pyang ALL_TARGETS := allyangs.tree allyangs_tree.html ALL_TARGETS += sonic_allyangs.tree sonic_allyangs_tree.html -ALL_TARGETS += $(YANG_API_IGNORES) #$(YANG_ANNOTS_CHK) +ALL_TARGETS += #$(YANG_ANNOTS_CHK) all: $(ALL_TARGETS) -%/.: - mkdir -p $@ - -.SECONDEXPANSION: #====================================================================== # Syntax check for annotation files @@ -61,7 +54,7 @@ all: $(ALL_TARGETS) .PHONY: annot annot: $(YANG_ANNOTS_CHK) -$(YANG_ANNOTS_CHK): $(YANG_ANNOTATION_FILES) $(YANG_MOD_FILES) $(YANG_COMMON_FILES) | $$(@D)/. +$(YANG_ANNOTS_CHK): $(YANG_ANNOTATION_FILES) $(YANG_MOD_FILES) $(YANG_COMMON_FILES) $(PYANG) --strict -f tree \ -p $(YANGDIR_COMMON):$(YANGDIR):$(YANGDIR_EXTENSIONS):$(YANGDIR_SONIC) \ $(YANG_ANNOTATION_FILES) > /dev/null @@ -107,13 +100,6 @@ sonic_allyangs_tree.html: $(SONIC_YANG_MOD_FILES) $(SONIC_YANG_COMMON_FILES) $(SONIC_YANG_MOD_FILES) @echo "+++++ Generation of HTML tree for Sonic Yang modules completed +++++" -#====================================================================== -# Generate api_ignore file -#====================================================================== -$(YANG_API_IGNORES): $(YANG_ANNOTATION_FILES) | $$(@D)/. - echo "# Yangs ignored" > $@ - (cd $(YANGDIR_ANNOTATIONS) && ls -f *.yang) >> $@ - #====================================================================== # Cleanups #====================================================================== From 4f4927f3aec0043132c135ba96ea0d76b26012e0 Mon Sep 17 00:00:00 2001 From: Sachin Holla Date: Wed, 10 Jun 2020 21:52:04 -0700 Subject: [PATCH 3/4] Cherry pick 32fa9d8 and 5bd7a97 from sonic-mgmt-framework --- translib/intf_app.go | 4 + translib/pfm_app.go | 272 ++++++++++++++++++++++++--------------- translib/pfm_app_test.go | 135 +++++++++++++++++++ 3 files changed, 309 insertions(+), 102 deletions(-) create mode 100644 translib/pfm_app_test.go diff --git a/translib/intf_app.go b/translib/intf_app.go index 94b9b1abf..8e417bb88 100644 --- a/translib/intf_app.go +++ b/translib/intf_app.go @@ -408,12 +408,16 @@ func (app *IntfApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { payload, err = dumpIetfJson(intfObj, false) } else { dummyifInfo := &ocbinds.OpenconfigInterfaces_Interfaces_Interface{} + counter_all_val := &ocbinds.OpenconfigInterfaces_Interfaces_Interface_State{} if *app.ygotTarget == ifInfo.Config { dummyifInfo.Config = ifInfo.Config payload, err = dumpIetfJson(dummyifInfo, false) } else if *app.ygotTarget == ifInfo.State { dummyifInfo.State = ifInfo.State payload, err = dumpIetfJson(dummyifInfo, false) + } else if *app.ygotTarget == ifInfo.State.Counters { + counter_all_val.Counters = ifInfo.State.Counters + payload, err = dumpIetfJson(counter_all_val, false) } else { log.Info("Not supported get type!") err = errors.New("Requested get-type not supported!") diff --git a/translib/pfm_app.go b/translib/pfm_app.go index 36f582b8b..f823d6c4a 100644 --- a/translib/pfm_app.go +++ b/translib/pfm_app.go @@ -20,14 +20,11 @@ package translib import ( "reflect" - "encoding/json" + "strconv" "errors" "github.com/Azure/sonic-mgmt-common/translib/db" "github.com/Azure/sonic-mgmt-common/translib/ocbinds" "github.com/openconfig/ygot/ygot" - "os" - "github.com/Azure/sonic-mgmt-common/translib/tlerr" - "io/ioutil" log "github.com/golang/glog" ) @@ -36,7 +33,8 @@ type PlatformApp struct { reqData []byte ygotRoot *ygot.GoStruct ygotTarget *interface{} - + eepromTs *db.TableSpec + eepromTable map[string]dbEntry } @@ -65,6 +63,7 @@ func (app *PlatformApp) initialize(data appData) { app.reqData = data.payload app.ygotRoot = data.ygotRoot app.ygotTarget = data.ygotTarget + app.eepromTs = &db.TableSpec{Name: "EEPROM_INFO"} } @@ -150,9 +149,33 @@ func (app *PlatformApp) processDelete(d *db.DB) (SetResponse, error) { func (app *PlatformApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { pathInfo := app.path - var payload []byte log.Infof("Received GET for PlatformApp Template: %s ,path: %s, vars: %v", pathInfo.Template, pathInfo.Path, pathInfo.Vars) + + stateDb := dbs[db.StateDB] + + var payload []byte + + // Read eeprom info from DB + app.eepromTable = make(map[string]dbEntry) + + tbl, derr := stateDb.GetTable(app.eepromTs) + if derr != nil { + log.Error("EEPROM_INFO table get failed!") + return GetResponse{Payload: payload}, derr + } + + keys, _ := tbl.GetKeys() + for _, key := range keys { + e, kerr := tbl.GetEntry(key) + if kerr != nil { + log.Error("EEPROM_INFO entry get failed!") + return GetResponse{Payload: payload}, kerr + } + + app.eepromTable[key.Get(0)] = dbEntry{entry: e} + } + targetUriPath, perr := getYangPathFromUri(app.path.Path) if perr != nil { log.Infof("getYangPathFromUri failed.") @@ -167,57 +190,102 @@ func (app *PlatformApp) processGet(dbs [db.MaxDB]*db.DB) (GetResponse, error) { } /////////////////////////// -func (app *PlatformApp) doGetSysEeprom() (GetResponse, error) { - - return app.getSysEepromJson() -} /** -Structures to read syseeprom from json file +Structures to read syseeprom from redis-db */ -type JSONEeprom struct { - Product_Name string `json:"Product Name"` - Part_Number string `json:"Part Number"` - Serial_Number string `json:"Serial Number"` - Base_MAC_Address string `json:"Base MAC Address"` - Manufacture_Date string `json:"Manufacture Date"` - Device_Version string `json:"Device Version"` - Label_Revision string `json:"Label Revision"` - Platform_Name string `json:"Platform Name"` - ONIE_Version string `json:"ONIE Version"` - MAC_Addresses int `json:"MAC Addresses"` - Manufacturer string `json:"Manufacturer"` - Manufacture_Country string `json:"Manufacture Country"` - Vendor_Name string `json:"Vendor Name"` - Diag_Version string `json:"Diag Version"` - Service_Tag string `json:"Service Tag"` - Vendor_Extension string `json:"Vendor Extension"` - Magic_Number int `json:"Magic Number"` - Card_Type string `json:"Card Type"` - Hardware_Version string `json:"Hardware Version"` - Software_Version string `json:"Software Version"` - Model_Name string `json:"Model Name"` +type EepromDb struct { + Product_Name string + Part_Number string + Serial_Number string + Base_MAC_Address string + Manufacture_Date string + Device_Version string + Label_Revision string + Platform_Name string + ONIE_Version string + MAC_Addresses int + Manufacturer string + Manufacture_Country string + Vendor_Name string + Diag_Version string + Service_Tag string + Vendor_Extension string + Magic_Number int + Card_Type string + Hardware_Version string + Software_Version string + Model_Name string } -func (app *PlatformApp) getSysEepromFromFile (eeprom *ocbinds.OpenconfigPlatform_Components_Component_State, all bool) (error) { - - log.Infof("getSysEepromFromFile Enter") - jsonFile, err := os.Open("/mnt/platform/syseeprom") - if err != nil { - log.Infof("syseeprom.json open failed") - errStr := "Information not available or Not supported" - terr := tlerr.NotFoundError{Format: errStr} - return terr +func (app *PlatformApp) getEepromDbObj () (EepromDb){ + log.Infof("parseEepromDb Enter") + + var eepromDbObj EepromDb + + for epItem, _ := range app.eepromTable { + e := app.eepromTable[epItem].entry + name := e.Get("Name") + + switch name { + case "Device Version": + eepromDbObj.Device_Version = e.Get("Value") + case "Service Tag": + eepromDbObj.Service_Tag = e.Get("Value") + case "Vendor Extension": + eepromDbObj.Vendor_Extension = e.Get("Value") + case "Magic Number": + mag, _ := strconv.ParseInt(e.Get("Value"), 10, 64) + eepromDbObj.Magic_Number = int(mag) + case "Card Type": + eepromDbObj.Card_Type = e.Get("Value") + case "Hardware Version": + eepromDbObj.Hardware_Version = e.Get("Value") + case "Software Version": + eepromDbObj.Software_Version = e.Get("Value") + case "Model Name": + eepromDbObj.Model_Name = e.Get("Value") + case "ONIE Version": + eepromDbObj.ONIE_Version = e.Get("Value") + case "Serial Number": + eepromDbObj.Serial_Number = e.Get("Value") + case "Vendor Name": + eepromDbObj.Vendor_Name = e.Get("Value") + case "Manufacturer": + eepromDbObj.Manufacturer = e.Get("Value") + case "Manufacture Country": + eepromDbObj.Manufacture_Country = e.Get("Value") + case "Platform Name": + eepromDbObj.Platform_Name = e.Get("Value") + case "Diag Version": + eepromDbObj.Diag_Version = e.Get("Value") + case "Label Revision": + eepromDbObj.Label_Revision = e.Get("Value") + case "Part Number": + eepromDbObj.Part_Number = e.Get("Value") + case "Product Name": + eepromDbObj.Product_Name = e.Get("Value") + case "Base MAC Address": + eepromDbObj.Base_MAC_Address = e.Get("Value") + case "Manufacture Date": + eepromDbObj.Manufacture_Date = e.Get("Value") + case "MAC Addresses": + mac, _ := strconv.ParseInt(e.Get("Value"), 10, 16) + eepromDbObj.MAC_Addresses = int(mac) + } } - defer jsonFile.Close() + return eepromDbObj +} + +func (app *PlatformApp) getSysEepromFromDb (eeprom *ocbinds.OpenconfigPlatform_Components_Component_State, all bool) (error) { + + log.Infof("getSysEepromFromDb Enter") - byteValue, _ := ioutil.ReadAll(jsonFile) - var jsoneeprom JSONEeprom + eepromDb := app.getEepromDbObj() - json.Unmarshal(byteValue, &jsoneeprom) empty := false removable := false name := "System Eeprom" @@ -230,52 +298,52 @@ func (app *PlatformApp) getSysEepromFromFile (eeprom *ocbinds.OpenconfigPlatform eeprom.OperStatus = ocbinds.OpenconfigPlatformTypes_COMPONENT_OPER_STATUS_ACTIVE eeprom.Location = &location - if jsoneeprom.Product_Name != "" { - eeprom.Id = &jsoneeprom.Product_Name + if eepromDb.Product_Name != "" { + eeprom.Id = &eepromDb.Product_Name } - if jsoneeprom.Part_Number != "" { - eeprom.PartNo = &jsoneeprom.Part_Number + if eepromDb.Part_Number != "" { + eeprom.PartNo = &eepromDb.Part_Number } - if jsoneeprom.Serial_Number != "" { - eeprom.SerialNo = &jsoneeprom.Serial_Number + if eepromDb.Serial_Number != "" { + eeprom.SerialNo = &eepromDb.Serial_Number } - if jsoneeprom.Base_MAC_Address != "" { + if eepromDb.Base_MAC_Address != "" { } - if jsoneeprom.Manufacture_Date != "" { - eeprom.MfgDate = &jsoneeprom.Manufacture_Date + if eepromDb.Manufacture_Date != "" { + eeprom.MfgDate = &eepromDb.Manufacture_Date } - if jsoneeprom.Label_Revision != "" { - eeprom.HardwareVersion = &jsoneeprom.Label_Revision + if eepromDb.Label_Revision != "" { + eeprom.HardwareVersion = &eepromDb.Label_Revision } - if jsoneeprom.Platform_Name != "" { - eeprom.Description = &jsoneeprom.Platform_Name + if eepromDb.Platform_Name != "" { + eeprom.Description = &eepromDb.Platform_Name } - if jsoneeprom.ONIE_Version != "" { + if eepromDb.ONIE_Version != "" { } - if jsoneeprom.MAC_Addresses != 0 { + if eepromDb.MAC_Addresses != 0 { } - if jsoneeprom.Manufacturer != "" { - eeprom.MfgName = &jsoneeprom.Manufacturer + if eepromDb.Manufacturer != "" { + eeprom.MfgName = &eepromDb.Manufacturer } - if jsoneeprom.Manufacture_Country != "" { + if eepromDb.Manufacture_Country != "" { } - if jsoneeprom.Vendor_Name != "" { + if eepromDb.Vendor_Name != "" { if eeprom.MfgName == nil { - eeprom.MfgName = &jsoneeprom.Vendor_Name + eeprom.MfgName = &eepromDb.Vendor_Name } } - if jsoneeprom.Diag_Version != "" { + if eepromDb.Diag_Version != "" { } - if jsoneeprom.Service_Tag != "" { + if eepromDb.Service_Tag != "" { if eeprom.SerialNo == nil { - eeprom.SerialNo = &jsoneeprom.Service_Tag + eeprom.SerialNo = &eepromDb.Service_Tag } } - if jsoneeprom.Hardware_Version != "" { - eeprom.HardwareVersion = &jsoneeprom.Hardware_Version + if eepromDb.Hardware_Version != "" { + eeprom.HardwareVersion = &eepromDb.Hardware_Version } - if jsoneeprom.Software_Version != "" { - eeprom.SoftwareVersion = &jsoneeprom.Software_Version + if eepromDb.Software_Version != "" { + eeprom.SoftwareVersion = &eepromDb.Software_Version } } else { targetUriPath, _ := getYangPathFromUri(app.path.Path) @@ -291,60 +359,60 @@ func (app *PlatformApp) getSysEepromFromFile (eeprom *ocbinds.OpenconfigPlatform case "/openconfig-platform:components/component/state/oper-status": eeprom.OperStatus = ocbinds.OpenconfigPlatformTypes_COMPONENT_OPER_STATUS_ACTIVE case "/openconfig-platform:components/component/state/id": - if jsoneeprom.Product_Name != "" { - eeprom.Id = &jsoneeprom.Product_Name + if eepromDb.Product_Name != "" { + eeprom.Id = &eepromDb.Product_Name } case "/openconfig-platform:components/component/state/part-no": - if jsoneeprom.Part_Number != "" { - eeprom.PartNo = &jsoneeprom.Part_Number + if eepromDb.Part_Number != "" { + eeprom.PartNo = &eepromDb.Part_Number } case "/openconfig-platform:components/component/state/serial-no": - if jsoneeprom.Serial_Number != "" { - eeprom.SerialNo = &jsoneeprom.Serial_Number + if eepromDb.Serial_Number != "" { + eeprom.SerialNo = &eepromDb.Serial_Number } - if jsoneeprom.Service_Tag != "" { + if eepromDb.Service_Tag != "" { if eeprom.SerialNo == nil { - eeprom.SerialNo = &jsoneeprom.Service_Tag + eeprom.SerialNo = &eepromDb.Service_Tag } } case "/openconfig-platform:components/component/state/mfg-date": - if jsoneeprom.Manufacture_Date != "" { - eeprom.MfgDate = &jsoneeprom.Manufacture_Date + if eepromDb.Manufacture_Date != "" { + eeprom.MfgDate = &eepromDb.Manufacture_Date } case "/openconfig-platform:components/component/state/hardware-version": - if jsoneeprom.Label_Revision != "" { - eeprom.HardwareVersion = &jsoneeprom.Label_Revision + if eepromDb.Label_Revision != "" { + eeprom.HardwareVersion = &eepromDb.Label_Revision } - if jsoneeprom.Hardware_Version != "" { + if eepromDb.Hardware_Version != "" { if eeprom.HardwareVersion == nil { - eeprom.HardwareVersion = &jsoneeprom.Hardware_Version + eeprom.HardwareVersion = &eepromDb.Hardware_Version } } case "/openconfig-platform:components/component/state/description": - if jsoneeprom.Platform_Name != "" { - eeprom.Description = &jsoneeprom.Platform_Name + if eepromDb.Platform_Name != "" { + eeprom.Description = &eepromDb.Platform_Name } case "/openconfig-platform:components/component/state/mfg-name": - if jsoneeprom.Manufacturer != "" { - eeprom.MfgName = &jsoneeprom.Manufacturer + if eepromDb.Manufacturer != "" { + eeprom.MfgName = &eepromDb.Manufacturer } - if jsoneeprom.Vendor_Name != "" { + if eepromDb.Vendor_Name != "" { if eeprom.MfgName == nil { - eeprom.MfgName = &jsoneeprom.Vendor_Name + eeprom.MfgName = &eepromDb.Vendor_Name } } case "/openconfig-platform:components/component/state/software-version": - if jsoneeprom.Software_Version != "" { - eeprom.SoftwareVersion = &jsoneeprom.Software_Version + if eepromDb.Software_Version != "" { + eeprom.SoftwareVersion = &eepromDb.Software_Version } } } return nil } -func (app *PlatformApp) getSysEepromJson () (GetResponse, error) { +func (app *PlatformApp) doGetSysEeprom () (GetResponse, error) { - log.Infof("Preparing json for system eeprom"); + log.Infof("Preparing collection for system eeprom"); var payload []byte var err error @@ -355,7 +423,7 @@ func (app *PlatformApp) getSysEepromJson () (GetResponse, error) { case "/openconfig-platform:components": pf_comp,_ := pf_cpts.NewComponent("System Eeprom") ygot.BuildEmptyTree(pf_comp) - err = app.getSysEepromFromFile(pf_comp.State, true) + err = app.getSysEepromFromDb(pf_comp.State, true) if err != nil { return GetResponse{Payload: payload}, err } @@ -365,7 +433,7 @@ func (app *PlatformApp) getSysEepromJson () (GetResponse, error) { if compName == "" { pf_comp,_ := pf_cpts.NewComponent("System Eeprom") ygot.BuildEmptyTree(pf_comp) - err = app.getSysEepromFromFile(pf_comp.State, true) + err = app.getSysEepromFromDb(pf_comp.State, true) if err != nil { return GetResponse{Payload: payload}, err } @@ -377,7 +445,7 @@ func (app *PlatformApp) getSysEepromJson () (GetResponse, error) { pf_comp := pf_cpts.Component[compName] if pf_comp != nil { ygot.BuildEmptyTree(pf_comp) - err = app.getSysEepromFromFile(pf_comp.State, true) + err = app.getSysEepromFromDb(pf_comp.State, true) if err != nil { return GetResponse{Payload: payload}, err } @@ -392,7 +460,7 @@ func (app *PlatformApp) getSysEepromJson () (GetResponse, error) { pf_comp := pf_cpts.Component[compName] if pf_comp != nil { ygot.BuildEmptyTree(pf_comp) - err = app.getSysEepromFromFile(pf_comp.State, true) + err = app.getSysEepromFromDb(pf_comp.State, true) if err != nil { return GetResponse{Payload: payload}, err } @@ -413,7 +481,7 @@ func (app *PlatformApp) getSysEepromJson () (GetResponse, error) { pf_comp := pf_cpts.Component[compName] if pf_comp != nil { ygot.BuildEmptyTree(pf_comp) - err = app.getSysEepromFromFile(pf_comp.State, false) + err = app.getSysEepromFromDb(pf_comp.State, false) if err != nil { return GetResponse{Payload: payload}, err } diff --git a/translib/pfm_app_test.go b/translib/pfm_app_test.go new file mode 100644 index 000000000..73e1647ea --- /dev/null +++ b/translib/pfm_app_test.go @@ -0,0 +1,135 @@ +package translib + +import ( + "errors" + "fmt" + "testing" + "github.com/Azure/sonic-mgmt-common/translib/db" +) + +// TLV EEPROM Data Types +const ( + _TLV_CODE_PRODUCT_NAME = "0x21" + _TLV_CODE_PART_NUMBER = "0x22" + _TLV_CODE_SERIAL_NUMBER = "0x23" + _TLV_CODE_MAC_BASE = "0x24" + _TLV_CODE_MANUF_DATE = "0x25" + _TLV_CODE_DEVICE_VERSION = "0x26" + _TLV_CODE_LABEL_REVISION = "0x27" + _TLV_CODE_PLATFORM_NAME = "0x28" + _TLV_CODE_ONIE_VERSION = "0x29" + _TLV_CODE_MAC_SIZE = "0x2A" + _TLV_CODE_MANUF_NAME = "0x2B" + _TLV_CODE_MANUF_COUNTRY = "0x2C" + _TLV_CODE_VENDOR_NAME = "0x2D" + _TLV_CODE_DIAG_VERSION = "0x2E" + _TLV_CODE_SERVICE_TAG = "0x2F" + _TLV_CODE_VENDOR_EXT = "0xFD" + _TLV_CODE_CRC_32 = "0xFE" +) + +const ( + TEST_PRODUCT_NAME = "6776-64X-O-AC-F" + TEST_PART_NUMBER = "FP123454321PF" + TEST_PLATFORM_NAME = "x86_64-pfm_test-platform" + TEST_SERVICE_TAG = "6776X6776" + TEST_MANUF_NAME = "TestManufacture" +) + +type EepromEntry struct { + TlvType string + Name string + Value string +} + +var testStateDbList = [...]EepromEntry{ + {_TLV_CODE_PRODUCT_NAME, "Product Name", TEST_PRODUCT_NAME}, + {_TLV_CODE_PLATFORM_NAME, "Platform Name", TEST_PLATFORM_NAME}, + {_TLV_CODE_SERVICE_TAG, "Service Tag", TEST_SERVICE_TAG}, + {_TLV_CODE_MANUF_NAME, "Manufacturer", TEST_MANUF_NAME}, + {_TLV_CODE_PART_NUMBER, "Part Number", TEST_PART_NUMBER}, +} + +func init() { + fmt.Println("+++++ Init pfm_app_test +++++") + + if err := clearPfmDataFromDb(); err == nil { + fmt.Println("+++++ Removed All Platform Data from Db +++++") + } else { + fmt.Printf("Failed to remove All Platform Data from Db: %v", err) + } +} + +// This will test GET on /openconfig-platform:components +func Test_PfmApp_TopLevelPath(t *testing.T) { + url := "/openconfig-platform:components" + + t.Run("Default_Response_Top_Level", processGetRequest(url, bulkPfmShowDefaultResponse, false)) + + //Set the factory DB with pre-defined EEPROM_INFO entry + if err := createPfmFactoryDb(); err != nil { + fmt.Printf("Failed to add Platform Data to Db: %v", err) + } + + t.Run("Get_Full_Pfm_Tree_Top_Level", processGetRequest(url, bulkPfmShowAllJsonResponse, false)) +} + +// THis will delete Platform Table from DB +func clearPfmDataFromDb() error { + var err error + eepromTable := db.TableSpec{Name: "EEPROM_INFO"} + + d := getStateDB() + if d == nil { + err = errors.New("Failed to connect to state Db") + return err + } + if err = d.DeleteTable(&eepromTable); err != nil { + err = errors.New("Failed to delete Eeprom Table") + return err + } + return err +} + +func createPfmFactoryDb() error { + var err error + eepromTable := db.TableSpec{Name: "EEPROM_INFO"} + + d := getStateDB() + if d == nil { + err = errors.New("Failed to connect to state Db") + return err + } + + for _, dbItem := range testStateDbList { + ca := make([]string, 1, 1) + ca[0] = dbItem.TlvType + + akey := db.Key{Comp: ca} + avalue := db.Value{Field: map[string]string{ + "Name": dbItem.Name, + "Value": dbItem.Value, + }, + } + d.SetEntry(&eepromTable, akey, avalue) + } + return err +} + +func getStateDB() *db.DB { + stateDb, _ := db.NewDB(db.Options{ + DBNo: db.StateDB, + InitIndicator: "STATE_DB_INITIALIZED", + TableNameSeparator: "|", + KeySeparator: "|", + }) + + return stateDb +} + +/***************************************************************************/ +/////////// JSON Data for Tests /////////////// +/***************************************************************************/ +var bulkPfmShowDefaultResponse string = "{\"openconfig-platform:components\":{\"component\":[{\"name\":\"System Eeprom\",\"state\":{\"empty\":false,\"location\":\"Slot 1\",\"name\":\"System Eeprom\",\"oper-status\":\"openconfig-platform-types:ACTIVE\",\"removable\":false}}]}}" + +var bulkPfmShowAllJsonResponse string = "{\"openconfig-platform:components\":{\"component\":[{\"name\":\"System Eeprom\",\"state\":{\"description\":\"" + TEST_PLATFORM_NAME + "\",\"empty\":false,\"id\":\"" + TEST_PRODUCT_NAME + "\",\"location\":\"Slot 1\",\"mfg-name\":\"" + TEST_MANUF_NAME + "\",\"name\":\"System Eeprom\",\"oper-status\":\"openconfig-platform-types:ACTIVE\",\"part-no\":\"" + TEST_PART_NUMBER + "\",\"removable\":false,\"serial-no\":\"" + TEST_SERVICE_TAG + "\"}}]}}" From 49a72dcf520e30881f8b9806bfc3149a171dd884 Mon Sep 17 00:00:00 2001 From: Sachin Holla Date: Thu, 11 Jun 2020 05:02:35 -0700 Subject: [PATCH 4/4] CVL test schema fixes CVL gotest cases require both main cvl schema files and additional test shema files. Modified the makefile to copy the main schema files to build/cvl/testdata/schema directory, which will be used as the schema root by gotest. --- cvl/Makefile | 4 +++- cvl/testdata/schema/Makefile | 4 ---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/cvl/Makefile b/cvl/Makefile index f2786e5ba..28f0e2406 100644 --- a/cvl/Makefile +++ b/cvl/Makefile @@ -24,6 +24,7 @@ TOP_DIR := .. BUILD_DIR:=$(TOP_DIR)/build/cvl CVL_PKG=$(TOP_DIR)/build/pkg/linux_amd64/cvl.a +CVL_SCHEMA_DIR = $(BUILD_DIR)/schema CVL_TEST_DIR = $(TOP_DIR)/build/tests/cvl CVL_TEST_BIN = $(CVL_TEST_DIR)/cvl.test CVL_TEST_SCHEMA_DIR = $(CVL_TEST_DIR)/testdata/schema @@ -52,8 +53,9 @@ $(CVL_TEST_BIN): $(TEST_FILES) $(SRC_FILES) | test-schema schema: $(MAKE) -C schema -test-schema: +test-schema: | schema $(MAKE) -C testdata/schema + cp $(CVL_SCHEMA_DIR)/*.yin $(CVL_TEST_SCHEMA_DIR)/ tests: $(MAKE) -C tests diff --git a/cvl/testdata/schema/Makefile b/cvl/testdata/schema/Makefile index 3c2a0ae7f..93976f79f 100644 --- a/cvl/testdata/schema/Makefile +++ b/cvl/testdata/schema/Makefile @@ -25,7 +25,6 @@ sonic_yang_common=$(sonic_yang)/common pyang_plugin_dir=$(TOPDIR)/tools/pyang/pyang_plugins src_files=$(wildcard *.yang) -src_files+=$(wildcard $(sonic_yang_common)/*.yang) out_dir=$(TOPDIR)/build/tests/cvl/testdata/schema out=$(patsubst %.yang, $(out_dir)/%.yin, $(notdir $(src_files)) ) @@ -49,9 +48,6 @@ $(out_dir)/%.yin:%.yang | $(out_dir)/. pyang -p $(search_path) \ --plugindir $(pyang_plugin_dir) -f yin-cvl $$devOpt $< -o $@ -$(out_dir)/%.yin: $(sonic_yang_common)/%.yang | $(out_dir)/. - pyang -p $(search_path) --plugindir $(pyang_plugin_dir) -f yin-cvl $< -o $@ - %.tree:%.yang @echo "Generating `basename $@` ..." @devFile="`echo $< | cut -d . -f1`-dev.yang"; \