From ebece7a4cdb942ea1ff7582ceda0f8765c329c9b Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Thu, 15 Apr 2021 09:02:23 +0200
Subject: policy: T2425: re-implement "policy" tree from vyatta-cfg-quagga in
 XML/Python

---
 Makefile                                           |   2 -
 data/configd-include.json                          |   1 +
 data/templates/frr/policy.frr.tmpl                 | 282 +++++++++++++++++++++
 .../include/policy-list-action.xml.i               |  21 --
 .../include/policy-list-description.xml.i          |  11 -
 .../include/policy-list-rule-description.xml.i     |  11 -
 interface-definitions/include/policy/action.xml.i  |  21 ++
 .../include/policy/description.xml.i               |  11 +
 interface-definitions/include/policy/host.xml.i    |  14 +
 .../include/policy/inverse-mask.xml.i              |  14 +
 interface-definitions/include/policy/network.xml.i |  14 +
 interface-definitions/policy.xml.in                | 162 ++++--------
 python/vyos/template.py                            |  15 ++
 smoketest/scripts/cli/test_policy.py               |   9 +-
 src/conf_mode/policy.py                            |  98 +++----
 15 files changed, 472 insertions(+), 214 deletions(-)
 create mode 100644 data/templates/frr/policy.frr.tmpl
 delete mode 100644 interface-definitions/include/policy-list-action.xml.i
 delete mode 100644 interface-definitions/include/policy-list-description.xml.i
 delete mode 100644 interface-definitions/include/policy-list-rule-description.xml.i
 create mode 100644 interface-definitions/include/policy/action.xml.i
 create mode 100644 interface-definitions/include/policy/description.xml.i
 create mode 100644 interface-definitions/include/policy/host.xml.i
 create mode 100644 interface-definitions/include/policy/inverse-mask.xml.i
 create mode 100644 interface-definitions/include/policy/network.xml.i

diff --git a/Makefile b/Makefile
index b157de71c..ccdb212c2 100644
--- a/Makefile
+++ b/Makefile
@@ -39,12 +39,10 @@ interface_definitions: $(config_xml_obj)
 
 	# XXX: delete top level node.def's that now live in other packages
 	rm -f $(TMPL_DIR)/firewall/node.def
-	rm -f $(TMPL_DIR)/policy/node.def
 	rm -f $(TMPL_DIR)/system/node.def
 	rm -f $(TMPL_DIR)/vpn/node.def
 	rm -f $(TMPL_DIR)/vpn/ipsec/node.def
 	rm -rf $(TMPL_DIR)/vpn/nipsec
-	rm -rf $(TMPL_DIR)/npolicy
 
 	# XXX: test if there are empty node.def files - this is not allowed as these
 	# could mask help strings or mandatory priority statements
diff --git a/data/configd-include.json b/data/configd-include.json
index 4959e5020..f241d0cb6 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -30,6 +30,7 @@
 "nat.py",
 "nat66.py",
 "ntp.py",
+"policy.py",
 "policy-local-route.py",
 "protocols_bfd.py",
 "protocols_bgp.py",
diff --git a/data/templates/frr/policy.frr.tmpl b/data/templates/frr/policy.frr.tmpl
new file mode 100644
index 000000000..2d02efa52
--- /dev/null
+++ b/data/templates/frr/policy.frr.tmpl
@@ -0,0 +1,282 @@
+!
+{% if policy.access_list is defined and policy.access_list is not none %}
+{%   for acl, acl_config in policy.access_list.items() | natural_sort %}
+{%     if acl_config.description is defined and acl_config.description is not none %}
+access-list {{ acl }} remark {{ acl_config.description }}
+{%     endif %}
+{%     if acl_config.rule is defined and acl_config.rule is not none %}
+{%       for rule, rule_config in acl_config.rule.items() | natural_sort %}
+{%         set ip = '' %}
+{%         set src = '' %}
+{%         set src_mask = '' %}
+{%         if rule_config.source is defined and rule_config.source.any is defined %}
+{%           set src = 'any' %}
+{%         elif rule_config.source is defined and rule_config.source.host is defined and rule_config.source.host is not none %}
+{%           set src = 'host ' + rule_config.source.host %}
+{%         elif rule_config.source is defined and rule_config.source.network is defined and rule_config.source.network is not none %}
+{%           set src = rule_config.source.network %}
+{%           set src_mask = rule_config.source.inverse_mask %}
+{%         endif %}
+{%         set dst = '' %}
+{%         set dst_mask = '' %}
+{%         if (acl|int >= 100 and acl|int <= 199) or (acl|int >= 2000 and acl|int <= 2699) %}
+{%           set ip = 'ip' %}
+{%           set dst = 'any' %}
+{%           if rule_config.destination is defined and rule_config.destination.any is defined %}
+{%             set dst = 'any' %}
+{%           elif rule_config.destination is defined and rule_config.destination.host is defined and rule_config.destination.host is not none %}
+{%             set dst = 'host ' + rule_config.destination.host %}
+{%           elif rule_config.destination is defined and rule_config.destination.network is defined and rule_config.destination.network is not none %}
+{%             set dst = rule_config.destination.network %}
+{%             set dst_mask = rule_config.destination.inverse_mask %}
+{%           endif %}
+{%         endif %}
+access-list {{ acl }} seq {{ rule }} {{ rule_config.action }} {{ ip }} {{ src }} {{ src_mask }} {{ dst }} {{ dst_mask }}
+{%       endfor %}
+{%     endif %}
+{%   endfor %}
+{% endif %}
+!
+{% if policy.access_list6 is defined and policy.access_list6 is not none %}
+{%   for acl, acl_config in policy.access_list6.items() | natural_sort %}
+{%     if acl_config.description is defined and acl_config.description is not none %}
+ipv6 access-list {{ acl }} remark {{ acl_config.description }}
+{%     endif %}
+{%     if acl_config.rule is defined and acl_config.rule is not none %}
+{%       for rule, rule_config in acl_config.rule.items() | natural_sort %}
+{%         set src = '' %}
+{%         if rule_config.source is defined and rule_config.source.any is defined %}
+{%           set src = 'any' %}
+{%         elif rule_config.source is defined and rule_config.source.network is defined and rule_config.source.network is not none %}
+{%           set src = rule_config.source.network %}
+{%         endif %}
+ipv6 access-list {{ acl }} seq {{ rule }} {{ rule_config.action }} {{ src }} {{ 'exact-match' if rule_config.source.exact_match is defined }}
+{%       endfor %}
+{%     endif %}
+{%   endfor %}
+{% endif %}
+!
+{% if policy.as_path_list is defined and policy.as_path_list is not none %}
+{%   for acl, acl_config in policy.as_path_list.items() | natural_sort %}
+{%     if acl_config.rule is defined and acl_config.rule is not none %}
+{%       for rule, rule_config in acl_config.rule.items() | natural_sort %}
+bgp as-path access-list {{ acl }} {{ rule_config.action }} {{ rule_config.regex }}
+{%       endfor %}
+{%     endif %}
+{%   endfor %}
+{% endif %}
+!
+{% if policy.community_list is defined and policy.community_list is not none %}
+{%   for list, list_config in policy.community_list.items() | natural_sort %}
+{%     if list_config.rule is defined and list_config.rule is not none %}
+{%       for rule, rule_config in list_config.rule.items() | natural_sort %}
+{#         by default, if casting to int fails it returns 0 #}
+{%         if list|int != 0 %}
+bgp community-list {{ list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }}
+{%         else %}
+bgp community-list expanded {{ list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }}
+{%         endif %}
+{%       endfor %}
+{%     endif %}
+{%   endfor %}
+{% endif %}
+!
+{% if policy.extcommunity_list is defined and policy.extcommunity_list is not none %}
+{%   for list, list_config in policy.extcommunity_list.items() | natural_sort %}
+{%     if list_config.rule is defined and list_config.rule is not none %}
+{%       for rule, rule_config in list_config.rule.items() | natural_sort %}
+{#         by default, if casting to int fails it returns 0 #}
+{%         if list|int != 0 %}
+bgp extcommunity-list {{ list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }}
+{%         else %}
+bgp extcommunity-list expanded {{ list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }}
+{%         endif %}
+{%       endfor %}
+{%     endif %}
+{%   endfor %}
+{% endif %}
+!
+{% if policy.large_community_list is defined and policy.large_community_list is not none %}
+{%   for list, list_config in policy.large_community_list.items() | natural_sort %}
+{%     if list_config.rule is defined and list_config.rule is not none %}
+{%       for rule, rule_config in list_config.rule.items() | natural_sort %}
+{#         by default, if casting to int fails it returns 0 #}
+{%         if list|int != 0 %}
+bgp large-community-list {{ list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }}
+{%         else %}
+bgp large-community-list expanded {{ list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }}
+{%         endif %}
+{%       endfor %}
+{%     endif %}
+{%   endfor %}
+{% endif %}
+!
+{% if policy.prefix_list is defined and policy.prefix_list is not none %}
+{%   for prefix_list, prefix_list_config in policy.prefix_list.items() | natural_sort %}
+{%     if prefix_list_config.description is defined and prefix_list_config.description is not none %}
+ip prefix-list {{ prefix_list }} description {{ prefix_list_config.description }}
+{%     endif %}
+{%     if prefix_list_config.rule is defined and prefix_list_config.rule is not none %}
+{%       for rule, rule_config in prefix_list_config.rule.items() | natural_sort %}
+ip prefix-list {{ prefix_list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.prefix }} {{ 'ge ' + rule_config.ge if rule_config.ge is defined }} {{ 'le ' + rule_config.le if rule_config.le is defined }}
+{%       endfor %}
+{%     endif %}
+{%   endfor %}
+{% endif %}
+!
+{% if policy.prefix_list6 is defined and policy.prefix_list6 is not none %}
+{%   for prefix_list, prefix_list_config in policy.prefix_list6.items() | natural_sort %}
+{%     if prefix_list_config.description is defined and prefix_list_config.description is not none %}
+ipv6 prefix-list {{ prefix_list }} description {{ prefix_list_config.description }}
+{%     endif %}
+{%     if prefix_list_config.rule is defined and prefix_list_config.rule is not none %}
+{%       for rule, rule_config in prefix_list_config.rule.items() | natural_sort %}
+ipv6 prefix-list {{ prefix_list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.prefix }} {{ 'ge ' + rule_config.ge if rule_config.ge is defined }} {{ 'le ' + rule_config.le if rule_config.le is defined }}
+{%       endfor %}
+{%     endif %}
+{%   endfor %}
+{% endif %}
+!
+{% if policy.route_map is defined and policy.route_map is not none %}
+{%   for route_map, route_map_config in policy.route_map.items() | natural_sort %}
+{%     if route_map_config.rule is defined and route_map_config.rule is not none %}
+{%       for rule, rule_config in route_map_config.rule.items() | natural_sort %}
+route-map {{ route_map }} {{ rule_config.action }} {{ rule }}
+{%         if rule_config.call is defined and rule_config.call is not none %}
+ call {{ rule_config.call }}
+{%         endif %}
+{%         if rule_config.continue is defined and rule_config.continue is not none %}
+ on-match goto {{ rule_config.continue }}
+{%         endif %}
+{%         if rule_config.description is defined and rule_config.description is not none %}
+ description {{ rule_config.description }}
+{%         endif %}
+{%         if rule_config.match is defined and rule_config.match is not none %}
+{%           if rule_config.match.as_path is defined and rule_config.match.as_path is not none %}
+ match as-path {{ rule_config.match.as_path }}
+{%           endif %}
+{%           if rule_config.match.community is defined and rule_config.match.community.community_list is defined and rule_config.match.community.community_list is not none %}
+ match community {{ rule_config.match.community.community_list }} {{ 'exact-match' if rule_config.match.community.exact_match is defined }}
+{%           endif %}
+{%           if rule_config.match.extcommunity is defined and rule_config.match.extcommunity is not none %}
+ match extcommunity {{ rule_config.match.extcommunity }}
+{%           endif %}
+{%           if rule_config.match.interface is defined and rule_config.match.interface is not none %}
+ match interface {{ rule_config.match.interface }}
+{%           endif %}
+{%           if rule_config.match.ip is defined and rule_config.match.ip.address is defined and rule_config.match.ip.address.access_list is defined and rule_config.match.ip.address.access_list is not none %}
+ match ip address {{ rule_config.match.ip.address.access_list }}
+{%           endif %}
+{%           if rule_config.match.ip is defined and rule_config.match.ip.address is defined and rule_config.match.ip.address.prefix_list is defined and rule_config.match.ip.address.prefix_list is not none %}
+ match ip address prefix-list {{ rule_config.match.ip.address.prefix_list }}
+{%           endif %}
+{%           if rule_config.match.ip is defined and rule_config.match.ip.nexthop is defined and rule_config.match.ip.nexthop.access_list is defined and rule_config.match.ip.nexthop.access_list is not none %}
+ match ip next-hop {{ rule_config.match.ip.nexthop.access_list }}
+{%           endif %}
+{%           if rule_config.match.ip is defined and rule_config.match.ip.nexthop is defined and rule_config.match.ip.nexthop.prefix_list is defined and rule_config.match.ip.nexthop.prefix_list is not none %}
+ match ip next-hop prefix-list {{ rule_config.match.ip.nexthop.prefix_list }}
+{%           endif %}
+{%           if rule_config.match.ip is defined and rule_config.match.ip.route_source is defined and rule_config.match.ip.route_source.access_list is defined and rule_config.match.ip.route_source.access_list is not none %}
+ match ip route-source {{ rule_config.match.ip.route_source.access_list }}
+{%           endif %}
+{%           if rule_config.match.ip is defined and rule_config.match.ip.route_source is defined and rule_config.match.ip.route_source.prefix_list is defined and rule_config.match.ip.route_source.prefix_list is not none %}
+ match ip route-source prefix-list {{ rule_config.match.ip.route_source.prefix_list }}
+{%           endif %}
+{%           if rule_config.match.ipv6 is defined and rule_config.match.ipv6.address is defined and rule_config.match.ipv6.address.access_list is defined and rule_config.match.ipv6.address.access_list is not none %}
+ match ipv6 address {{ rule_config.match.ipv6.address.access_list }}
+{%           endif %}
+{%           if rule_config.match.ipv6 is defined and rule_config.match.ipv6.address is defined and rule_config.match.ipv6.address.prefix_list is defined and rule_config.match.ipv6.address.prefix_list is not none %}
+ match ipv6 address prefix-list {{ rule_config.match.ipv6.address.prefix_list }}
+{%           endif %}
+{%           if rule_config.match.ipv6 is defined and rule_config.match.ipv6.nexthop is defined and rule_config.match.ipv6.nexthop is not none %}
+ match ipv6 next-hop {{ rule_config.match.ipv6.nexthop }}
+{%           endif %}
+{%           if rule_config.match.large_community is defined and rule_config.match.large_community.large_community_list is defined and rule_config.match.large_community.large_community_list is not none %}
+ match large-community {{ rule_config.match.large_community.large_community_list }}
+{%           endif %}
+{%           if rule_config.match.local_preference is defined and rule_config.match.local_preference is not none %}
+ match local-preference {{ rule_config.match.local_preference }}
+{%           endif %}
+{%           if rule_config.match.metric is defined and rule_config.match.metric is not none %}
+ match metric {{ rule_config.match.metric }}
+{%           endif %}
+{%           if rule_config.match.origin is defined and rule_config.match.origin is not none %}
+ match origin {{ rule_config.match.origin }}
+{%           endif %}
+{%           if rule_config.match.peer is defined and rule_config.match.peer is not none %}
+ match peer {{ rule_config.match.peer }}
+{%           endif %}
+{%           if rule_config.match.rpki is defined and rule_config.match.rpki is not none %}
+ match rpki {{ rule_config.match.rpki }}
+{%           endif %}
+{%           if rule_config.match.tag is defined and rule_config.match.tag is not none %}
+ match tag {{ rule_config.match.tag }}
+{%           endif %}
+{%         endif %}
+{%         if rule_config.on_match is defined and rule_config.on_match is not none %}
+{%           if rule_config.on_match.next is defined %}
+ on-match next
+{%           endif %}
+{%           if rule_config.on_match.goto is defined and rule_config.on_match.goto is not none %}
+ on-match goto {{ rule_config.on_match.goto }}
+{%           endif %}
+{%         endif %}
+{%         if rule_config.set is defined and rule_config.set is not none %}
+{%           if rule_config.set.aggregator is defined and rule_config.set.aggregator.as is defined and rule_config.set.aggregator.ip is defined %}
+ set aggregator as {{ rule_config.set.aggregator.as }} {{ rule_config.set.aggregator.ip }}
+{%           endif %}
+{%           if rule_config.set.as_path_exclude is defined and rule_config.set.as_path_exclude is not none %}
+ set as-path exclude {{ rule_config.set.as_path_exclude }}
+{%           endif %}
+{%           if rule_config.set.as_path_prepend is defined and rule_config.set.as_path_prepend is not none %}
+ set as-path prepend {{ rule_config.set.as_path_prepend }}
+{%           endif %}
+{%           if rule_config.set.atomic_aggregate is defined %}
+ set atomic-aggregate
+{%           endif %}
+{%           if rule_config.set.distance is defined and rule_config.set.distance is not none %}
+ set distance {{ rule_config.set.distance }}
+{%           endif %}
+{%           if rule_config.set.ip_next_hop is defined and rule_config.set.ip_next_hop is not none %}
+ set ip next-hop {{ rule_config.set.ip_next_hop }}
+{%           endif %}
+{%           if rule_config.set.ipv6_next_hop is defined and rule_config.set.ipv6_next_hop.global is defined and rule_config.set.ipv6_next_hop.global is not none %}
+ set ipv6 next-hop global {{ rule_config.set.ipv6_next_hop.global }}
+{%           endif %}
+{%           if rule_config.set.ipv6_next_hop is defined and rule_config.set.ipv6_next_hop.local is defined and rule_config.set.ipv6_next_hop.local is not none %}
+ set ipv6 next-hop local {{ rule_config.set.ipv6_next_hop.local }}
+{%           endif %}
+{%           if rule_config.set.large_community is defined and rule_config.set.large_community is not none %}
+ set large-community {{ rule_config.set.large_community }}
+{%           endif %}
+{%           if rule_config.set.local_preference is defined and rule_config.set.local_preference is not none %}
+ set local-preference {{ rule_config.set.local_preference }}
+{%           endif %}
+{%           if rule_config.set.metric is defined and rule_config.set.metric is not none %}
+ set metric {{ rule_config.set.metric }}
+{%           endif %}
+{%           if rule_config.set.metric_type is defined and rule_config.set.metric_type is not none %}
+ set metric-type {{ rule_config.set.metric_type }}
+{%           endif %}
+{%           if rule_config.set.origin is defined and rule_config.set.origin is not none %}
+ set origin {{ rule_config.set.origin }}
+{%           endif %}
+{%           if rule_config.set.originator_id is defined and rule_config.set.originator_id is not none %}
+ set originator-id {{ rule_config.set.originator_id }}
+{%           endif %}
+{%           if rule_config.set.src is defined and rule_config.set.src is not none %}
+ set src {{ rule_config.set.src }}
+{%           endif %}
+{%           if rule_config.set.tag is defined and rule_config.set.tag is not none %}
+ set tag {{ rule_config.set.tag }}
+{%           endif %}
+{%           if rule_config.set.weight is defined and rule_config.set.weight is not none %}
+ set weight {{ rule_config.set.weight }}
+{%           endif %}
+{%         endif %}
+{%       endfor %}
+!
+{%     endif %}
+{%   endfor %}
+{% endif %}
+!
diff --git a/interface-definitions/include/policy-list-action.xml.i b/interface-definitions/include/policy-list-action.xml.i
deleted file mode 100644
index fddbd5a98..000000000
--- a/interface-definitions/include/policy-list-action.xml.i
+++ /dev/null
@@ -1,21 +0,0 @@
-<!-- included start from policy-list-action.xml.i -->
-<leafNode name="action">
-  <properties>
-    <help>Action to take on entries matching this rule [REQUIRED]</help>
-    <completionHelp>
-      <list>permit deny</list>
-    </completionHelp>
-    <valueHelp>
-      <format>permit</format>
-      <description>Permit matching entries</description>
-    </valueHelp>
-    <valueHelp>
-      <format>deny</format>
-      <description>Deny matching entries</description>
-    </valueHelp>
-    <constraint>
-      <regex>^(permit|deny)$</regex>
-    </constraint>
-  </properties>
-</leafNode>
-<!-- included end -->
diff --git a/interface-definitions/include/policy-list-description.xml.i b/interface-definitions/include/policy-list-description.xml.i
deleted file mode 100644
index a50278729..000000000
--- a/interface-definitions/include/policy-list-description.xml.i
+++ /dev/null
@@ -1,11 +0,0 @@
-<!-- included start from policy-list-description.xml.i -->
-<leafNode name="description">
-  <properties>
-    <help>Description for this policy</help>
-    <valueHelp>
-      <format>txt</format>
-      <description>Description for this policy</description>
-    </valueHelp>
-  </properties>
-</leafNode>
-<!-- included end -->
diff --git a/interface-definitions/include/policy-list-rule-description.xml.i b/interface-definitions/include/policy-list-rule-description.xml.i
deleted file mode 100644
index e22fb7c28..000000000
--- a/interface-definitions/include/policy-list-rule-description.xml.i
+++ /dev/null
@@ -1,11 +0,0 @@
-<!-- included start from policy-list-rule-description.xml.i -->
-<leafNode name="description">
-  <properties>
-    <help>Description for this rule</help>
-    <valueHelp>
-      <format>txt</format>
-      <description>Description for this rule</description>
-    </valueHelp>
-  </properties>
-</leafNode>
-<!-- included end -->
diff --git a/interface-definitions/include/policy/action.xml.i b/interface-definitions/include/policy/action.xml.i
new file mode 100644
index 000000000..fddbd5a98
--- /dev/null
+++ b/interface-definitions/include/policy/action.xml.i
@@ -0,0 +1,21 @@
+<!-- included start from policy-list-action.xml.i -->
+<leafNode name="action">
+  <properties>
+    <help>Action to take on entries matching this rule [REQUIRED]</help>
+    <completionHelp>
+      <list>permit deny</list>
+    </completionHelp>
+    <valueHelp>
+      <format>permit</format>
+      <description>Permit matching entries</description>
+    </valueHelp>
+    <valueHelp>
+      <format>deny</format>
+      <description>Deny matching entries</description>
+    </valueHelp>
+    <constraint>
+      <regex>^(permit|deny)$</regex>
+    </constraint>
+  </properties>
+</leafNode>
+<!-- included end -->
diff --git a/interface-definitions/include/policy/description.xml.i b/interface-definitions/include/policy/description.xml.i
new file mode 100644
index 000000000..15a77cd66
--- /dev/null
+++ b/interface-definitions/include/policy/description.xml.i
@@ -0,0 +1,11 @@
+<!-- included start from policy/description.xml.i -->
+<leafNode name="description">
+  <properties>
+    <help>Description</help>
+    <valueHelp>
+      <format>txt</format>
+      <description>Description</description>
+    </valueHelp>
+  </properties>
+</leafNode>
+<!-- included end -->
diff --git a/interface-definitions/include/policy/host.xml.i b/interface-definitions/include/policy/host.xml.i
new file mode 100644
index 000000000..ac017c630
--- /dev/null
+++ b/interface-definitions/include/policy/host.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from policy/host.xml.i -->
+<leafNode name="host">
+  <properties>
+    <help>Single host IP address to match</help>
+    <valueHelp>
+      <format>ipv4</format>
+      <description>Host address to match</description>
+    </valueHelp>
+    <constraint>
+      <validator name="ipv4-address"/>
+    </constraint>
+  </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/policy/inverse-mask.xml.i b/interface-definitions/include/policy/inverse-mask.xml.i
new file mode 100644
index 000000000..cec69a81b
--- /dev/null
+++ b/interface-definitions/include/policy/inverse-mask.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from policy/inverse-mask.xml.i -->
+<leafNode name="inverse-mask">
+  <properties>
+    <help>Network/netmask to match (requires network be defined)</help>
+    <valueHelp>
+      <format>ipv4</format>
+      <description>Inverse-mask to match</description>
+    </valueHelp>
+    <constraint>
+      <validator name="ipv4-address"/>
+    </constraint>
+  </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/policy/network.xml.i b/interface-definitions/include/policy/network.xml.i
new file mode 100644
index 000000000..f2aea6be8
--- /dev/null
+++ b/interface-definitions/include/policy/network.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from policy/network.xml.i -->
+<leafNode name="network">
+  <properties>
+    <help>Network/netmask to match (requires inverse-mask be defined)</help>
+    <valueHelp>
+      <format>ipv4net</format>
+      <description>Inverse-mask to match</description>
+    </valueHelp>
+    <constraint>
+      <validator name="ipv4-address"/>
+    </constraint>
+  </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index 7cf20d3de..3a769dea1 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -1,15 +1,15 @@
 <?xml version="1.0"?>
-<!-- Policy access|prefix|route-map lists -->
 <interfaceDefinition>
-  <node name="npolicy" owner="${vyos_conf_scripts_dir}/policy.py">
+  <node name="policy" owner="${vyos_conf_scripts_dir}/policy.py">
     <properties>
+      <priority>470</priority>
       <help>Routing policy</help>
     </properties>
     <children>
       <tagNode name="access-list">
         <properties>
           <help>IP access-list filter</help>
-          <priority>470</priority>
+
           <valueHelp>
             <format>u32:1-99</format>
             <description>IP standard access list</description>
@@ -28,7 +28,7 @@
           </valueHelp>
         </properties>
         <children>
-          #include <include/policy-list-description.xml.i>
+          #include <include/policy/description.xml.i>
           <tagNode name="rule">
             <properties>
               <help>Rule for this access-list</help>
@@ -41,8 +41,8 @@
               </constraint>
             </properties>
             <children>
-              #include <include/policy-list-action.xml.i>
-              #include <include/policy-list-rule-description.xml.i>
+              #include <include/policy/action.xml.i>
+              #include <include/policy/description.xml.i>
               <node name="destination">
                 <properties>
                   <help>Destination network or address</help>
@@ -54,42 +54,9 @@
                       <valueless/>
                     </properties>
                   </leafNode>
-                  <leafNode name="host">
-                    <properties>
-                      <help>Single host IP address to match</help>
-                      <valueHelp>
-                        <format>ipv4</format>
-                        <description>Host address to match</description>
-                      </valueHelp>
-                      <constraint>
-                        <validator name="ipv4-host"/>
-                      </constraint>
-                    </properties>
-                  </leafNode>
-                  <leafNode name="inverse-mask">
-                    <properties>
-                      <help>Network/netmask to match (requires network be defined)</help>
-                      <valueHelp>
-                        <format>ipv4</format>
-                        <description>Inverse-mask to match</description>
-                      </valueHelp>
-                      <constraint>
-                        <validator name="ipv4-address"/>
-                      </constraint>
-                    </properties>
-                  </leafNode>
-                  <leafNode name="network">
-                    <properties>
-                      <help>Network/netmask to match (requires inverse-mask be defined)</help>
-                      <valueHelp>
-                        <format>ipv4net</format>
-                        <description>Inverse-mask to match</description>
-                      </valueHelp>
-                      <constraint>
-                        <validator name="ip-prefix"/>
-                      </constraint>
-                    </properties>
-                  </leafNode>
+                  #include <include/policy/host.xml.i>
+                  #include <include/policy/inverse-mask.xml.i>
+                  #include <include/policy/network.xml.i>
                 </children>
               </node>
               <node name="source">
@@ -103,42 +70,9 @@
                       <valueless/>
                     </properties>
                   </leafNode>
-                  <leafNode name="host">
-                    <properties>
-                      <help>Single host IP address to match</help>
-                      <valueHelp>
-                        <format>ipv4</format>
-                        <description>Host address to match</description>
-                      </valueHelp>
-                      <constraint>
-                        <validator name="ipv4-host"/>
-                      </constraint>
-                    </properties>
-                  </leafNode>
-                  <leafNode name="inverse-mask">
-                    <properties>
-                      <help>Network/netmask to match (requires network be defined)</help>
-                      <valueHelp>
-                        <format>ipv4</format>
-                        <description>Inverse-mask to match</description>
-                      </valueHelp>
-                      <constraint>
-                        <validator name="ipv4-address"/>
-                      </constraint>
-                    </properties>
-                  </leafNode>
-                  <leafNode name="network">
-                    <properties>
-                      <help>Network/netmask to match (requires inverse-mask be defined)</help>
-                      <valueHelp>
-                        <format>ipv4net</format>
-                        <description>Inverse-mask to match</description>
-                      </valueHelp>
-                      <constraint>
-                        <validator name="ip-prefix"/>
-                      </constraint>
-                    </properties>
-                  </leafNode>
+                  #include <include/policy/host.xml.i>
+                  #include <include/policy/inverse-mask.xml.i>
+                  #include <include/policy/network.xml.i>
                 </children>
               </node>
             </children>
@@ -148,14 +82,13 @@
       <tagNode name="access-list6">
         <properties>
           <help>IPv6 access-list filter</help>
-          <priority>470</priority>
           <valueHelp>
             <format>txt</format>
             <description>Name of IPv6 access-list</description>
           </valueHelp>
         </properties>
         <children>
-          #include <include/policy-list-description.xml.i>
+          #include <include/policy/description.xml.i>
           <tagNode name="rule">
             <properties>
               <help>Rule for this access-list6</help>
@@ -168,8 +101,8 @@
               </constraint>
             </properties>
             <children>
-              #include <include/policy-list-action.xml.i>
-              #include <include/policy-list-rule-description.xml.i>
+              #include <include/policy/action.xml.i>
+              #include <include/policy/description.xml.i>
               <node name="source">
                 <properties>
                   <help>Source IPv6 network to match</help>
@@ -208,14 +141,13 @@
       <tagNode name="as-path-list">
         <properties>
           <help>Border Gateway Protocol (BGP) autonomous system path filter</help>
-          <priority>470</priority>
           <valueHelp>
             <format>txt</format>
             <description>AS path list name</description>
           </valueHelp>
         </properties>
         <children>
-          #include <include/policy-list-description.xml.i>
+          #include <include/policy/description.xml.i>
           <tagNode name="rule">
             <properties>
               <help>Rule for this as-path-list</help>
@@ -228,8 +160,8 @@
               </constraint>
             </properties>
             <children>
-              #include <include/policy-list-action.xml.i>
-              #include <include/policy-list-rule-description.xml.i>
+              #include <include/policy/action.xml.i>
+              #include <include/policy/description.xml.i>
               <leafNode name="regex">
                 <properties>
                   <help>Regular expression to match against an AS path</help>
@@ -246,14 +178,13 @@
       <tagNode name="community-list">
         <properties>
           <help>Border Gateway Protocol (BGP) autonomous system path filter</help>
-          <priority>470</priority>
           <valueHelp>
             <format>txt</format>
             <description>Border Gateway Protocol (BGP) community-list filter</description>
           </valueHelp>
         </properties>
         <children>
-          #include <include/policy-list-description.xml.i>
+          #include <include/policy/description.xml.i>
           <tagNode name="rule">
             <properties>
               <help>Rule for this BGP community list</help>
@@ -266,8 +197,8 @@
               </constraint>
             </properties>
             <children>
-              #include <include/policy-list-action.xml.i>
-              #include <include/policy-list-rule-description.xml.i>
+              #include <include/policy/action.xml.i>
+              #include <include/policy/description.xml.i>
               <leafNode name="regex">
                 <properties>
                   <help>Regular expression to match against a community list</help>
@@ -291,7 +222,7 @@
           </valueHelp>
         </properties>
         <children>
-          #include <include/policy-list-description.xml.i>
+          #include <include/policy/description.xml.i>
           <tagNode name="rule">
             <properties>
               <help>Rule for this BGP extended community list</help>
@@ -304,8 +235,8 @@
               </constraint>
             </properties>
             <children>
-              #include <include/policy-list-action.xml.i>
-              #include <include/policy-list-rule-description.xml.i>
+              #include <include/policy/action.xml.i>
+              #include <include/policy/description.xml.i>
               <leafNode name="regex">
                 <properties>
                   <help>Regular expression to match against an extended community list</help>
@@ -330,14 +261,13 @@
       <tagNode name="large-community-list">
         <properties>
           <help>Border Gateway Protocol (BGP) large-community-list filter</help>
-          <priority>470</priority>
           <valueHelp>
             <format>txt</format>
             <description>Border Gateway Protocol (BGP) large-community-list filter</description>
           </valueHelp>
         </properties>
         <children>
-          #include <include/policy-list-description.xml.i>
+          #include <include/policy/description.xml.i>
           <tagNode name="rule">
             <properties>
               <help>Rule for this BGP extended community list</help>
@@ -350,8 +280,8 @@
               </constraint>
             </properties>
             <children>
-              #include <include/policy-list-action.xml.i>
-              #include <include/policy-list-rule-description.xml.i>
+              #include <include/policy/action.xml.i>
+              #include <include/policy/description.xml.i>
               <leafNode name="regex">
                 <properties>
                   <help>Regular expression to match against a large community list</help>
@@ -368,14 +298,13 @@
       <tagNode name="prefix-list">
         <properties>
           <help>IP prefix-list filter</help>
-          <priority>470</priority>
           <valueHelp>
             <format>txt</format>
             <description>Prefix list name</description>
           </valueHelp>
         </properties>
         <children>
-          #include <include/policy-list-description.xml.i>
+          #include <include/policy/description.xml.i>
           <tagNode name="rule">
             <properties>
               <help>Rule for this prefix-list</help>
@@ -388,8 +317,8 @@
               </constraint>
             </properties>
             <children>
-              #include <include/policy-list-action.xml.i>
-              #include <include/policy-list-rule-description.xml.i>
+              #include <include/policy/action.xml.i>
+              #include <include/policy/description.xml.i>
               <leafNode name="ge">
                 <properties>
                   <help>Prefix length to match a netmask greater than or equal to it</help>
@@ -433,14 +362,13 @@
       <tagNode name="prefix-list6">
         <properties>
           <help>IPv6 prefix-list filter</help>
-          <priority>470</priority>
           <valueHelp>
             <format>txt</format>
             <description>Prefix list name</description>
           </valueHelp>
         </properties>
         <children>
-          #include <include/policy-list-description.xml.i>
+          #include <include/policy/description.xml.i>
           <tagNode name="rule">
             <properties>
               <help>Rule for this prefix-list6</help>
@@ -453,8 +381,8 @@
               </constraint>
             </properties>
             <children>
-              #include <include/policy-list-action.xml.i>
-              #include <include/policy-list-rule-description.xml.i>
+              #include <include/policy/action.xml.i>
+              #include <include/policy/description.xml.i>
               <leafNode name="ge">
                 <properties>
                   <help>Prefix length to match a netmask greater than or equal to it</help>
@@ -498,14 +426,17 @@
       <tagNode name="route-map">
         <properties>
           <help>IP route-map</help>
-          <priority>470</priority>
           <valueHelp>
             <format>txt</format>
             <description>Route map name</description>
           </valueHelp>
+          <constraint>
+            <regex>^[-a-zA-Z0-9.]+$</regex>
+          </constraint>
+          <constraintErrorMessage>Route-map name can only contain alpha-numeric letters and a hyphen</constraintErrorMessage>
         </properties>
         <children>
-          #include <include/policy-list-description.xml.i>
+          #include <include/policy/description.xml.i>
           <tagNode name="rule">
             <properties>
               <help>Rule for this route-map</help>
@@ -518,7 +449,7 @@
               </constraint>
             </properties>
             <children>
-              #include <include/policy-list-action.xml.i>
+              #include <include/policy/action.xml.i>
               <leafNode name="call">
                 <properties>
                   <help>Call another route-map on match</help>
@@ -540,7 +471,7 @@
                   </valueHelp>
                 </properties>
               </leafNode>
-              #include <include/policy-list-rule-description.xml.i>
+              #include <include/policy/description.xml.i>
               <node name="match">
                 <properties>
                   <help>Route parameters to match</help>
@@ -759,11 +690,11 @@
                         <properties>
                           <help>IPv6 next-hop of route to match</help>
                           <valueHelp>
-                            <format>ipv4</format>
-                            <description>Peer IP address</description>
+                            <format>ipv6</format>
+                            <description>Nexthop IPv6 address</description>
                           </valueHelp>
                           <constraint>
-                            <validator name="ipv4-address"/>
+                            <validator name="ipv6-address"/>
                           </constraint>
                         </properties>
                       </leafNode>
@@ -962,6 +893,7 @@
                   <leafNode name="atomic-aggregate">
                     <properties>
                       <help>Border Gateway Protocol (BGP) atomic aggregate attribute</help>
+                      <valueless/>
                     </properties>
                   </leafNode>
                   <leafNode name="bgp-extcommunity-rt">
@@ -1198,7 +1130,7 @@
                         <description>Orignator IP address</description>
                       </valueHelp>
                       <constraint>
-                        <validator name="ipv4-host"/>
+                        <validator name="ipv4-address"/>
                       </constraint>
                     </properties>
                   </leafNode>
@@ -1214,8 +1146,8 @@
                         <description>IPv6 address</description>
                       </valueHelp>
                       <constraint>
-                        <validator name="ipv4-host"/>
-                        <validator name="ipv6-host"/>
+                        <validator name="ipv4-address"/>
+                        <validator name="ipv6-address"/>
                       </constraint>
                     </properties>
                   </leafNode>
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 7810f5edd..3fbb33acb 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -346,3 +346,18 @@ def get_dhcp_router(interface):
         if 'option routers' in line:
             (_, _, address) = line.split()
             return address.rstrip(';')
+
+@register_filter('natural_sort')
+def natural_sort(iterable):
+    import re
+    from jinja2.runtime import Undefined
+
+    if isinstance(iterable, Undefined) or iterable is None:
+        return list()
+
+    def convert(text):
+        return int(text) if text.isdigit() else text.lower()
+    def alphanum_key(key):
+        return [convert(c) for c in re.split('([0-9]+)', str(key))]
+
+    return sorted(iterable, key=alphanum_key)
diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py
index 57c1bb088..59425b789 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -767,9 +767,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
                     '10' : {
                         'action' : 'deny',
                         'set' : {
-# Disabled b/c of https://phabricator.vyos.net/T3479
-#                           'aggregator-as'       : '1234567890',
-#                           'aggregator-ip'       : '10.255.255.0',
+                            'aggregator-as'       : '1234567890',
+                            'aggregator-ip'       : '10.255.255.0',
                             'as-path-exclude'     : '1234',
                             'as-path-prepend'     : '1234567890 987654321',
                             'atomic-aggregate'    : '',
@@ -1036,12 +1035,12 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
                         tmp += 'atomic-aggregate'
                     elif 'distance' in rule_config['set']:
                         tmp += 'distance ' + rule_config['set']['distance']
+                    elif 'ip-next-hop' in rule_config['set']:
+                        tmp += 'ip next-hop ' + rule_config['set']['ip-next-hop']
                     elif 'ipv6-next-hop-global' in rule_config['set']:
                         tmp += 'ipv6 next-hop global ' + rule_config['set']['ipv6-next-hop-global']
                     elif 'ipv6-next-hop-local' in rule_config['set']:
                         tmp += 'ipv6 next-hop local ' + rule_config['set']['ipv6-next-hop-local']
-                    elif 'ip-next-hop' in rule_config['set']:
-                        tmp += 'ip next-hop ' + rule_config['set']['ip-next-hop']
                     elif 'large-community' in rule_config['set']:
                         tmp += 'large-community ' + rule_config['set']['large-community']
                     elif 'local-preference' in rule_config['set']:
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index 94a020e7b..f2e9010e3 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -20,47 +20,46 @@ from sys import exit
 
 from vyos.config import Config
 from vyos.configdict import dict_merge
-from vyos.template import render
 from vyos.template import render_to_string
-from vyos.util import call
 from vyos.util import dict_search
 from vyos import ConfigError
 from vyos import frr
 from vyos import airbag
-from pprint import pprint
 airbag.enable()
 
-config_file = r'/tmp/policy.frr'
-frr_daemon = 'zebra'
-
-DEBUG = os.path.exists('/tmp/policy.debug')
-if DEBUG:
-    import logging
-    lg = logging.getLogger("vyos.frr")
-    lg.setLevel(logging.DEBUG)
-    ch = logging.StreamHandler()
-    lg.addHandler(ch)
-
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
-    base = ['npolicy']
-    policy = conf.get_config_dict(base, key_mangling=('-', '_'))
 
-    # Bail out early if configuration tree does not exist
-    if not conf.exists(base):
-        return policy
-
-    pprint(policy)
-    exit(1)
+    base = ['policy']
+    policy = conf.get_config_dict(base, key_mangling=('-', '_'),
+                                  no_tag_node_value_mangle=True)
     return policy
 
 def verify(policy):
     if not policy:
         return None
 
+    if 'access-list' in policy:
+        for acl, acl_config in policy['access-list'].items():
+            if 'rule' not in acl_config:
+                continue
+
+            for rule, rule_config in acl_config['rule'].items():
+                if 'source' not in rule_config:
+                    raise ConfigError(f'Source must be specified for rule {rule} '\
+                                      f'for access-list {acl}!')
+                if 'action' not in rule_config:
+                    raise ConfigError(f'Action must be specified for rule {rule} '\
+                                      f'for access-list {acl}!')
+
+                if int(acl) not in range(100, 200) or int(acl) not in range(2000, 2700):
+                    if 'destination' not in rule_config:
+                        raise ConfigError(f'Destination must be specified for rule {rule} '\
+                                          f'for access-list {acl}!')
+
     return None
 
 def generate(policy):
@@ -68,41 +67,42 @@ def generate(policy):
         policy['new_frr_config'] = ''
         return None
 
-    # render(config) not needed, its only for debug
-  #  render(config_file, 'frr/policy.frr.tmpl', policy)
-  #  policy['new_frr_config'] = render_to_string('frr/policy.frr.tmpl')
-
+    policy['new_frr_config'] = render_to_string('frr/policy.frr.tmpl', policy)
     return None
 
 def apply(policy):
+    bgp_daemon = 'bgpd'
+    zebra_daemon = 'zebra'
+
     # Save original configuration prior to starting any commit actions
-   # frr_cfg = frr.FRRConfig()
-   # frr_cfg.load_configuration(frr_daemon)
-   # frr_cfg.modify_section(f'ip', '')
-   # frr_cfg.add_before(r'(line vty)', policy['new_frr_config'])
-
-    # Debugging
-    if DEBUG:
-        from pprint import pprint
-        print('')
-        print('--------- DEBUGGING ----------')
-        pprint(dir(frr_cfg))
-        print('Existing config:\n')
-        for line in frr_cfg.original_config:
-            print(line)
-        print(f'Replacement config:\n')
-        print(f'{policy["new_frr_config"]}')
-        print(f'Modified config:\n')
-        print(f'{frr_cfg}')
-
-   # frr_cfg.commit_configuration(frr_daemon)
+    frr_cfg = frr.FRRConfig()
+
+    # The route-map used for the FIB (zebra) is part of the zebra daemon
+    frr_cfg.load_configuration(bgp_daemon)
+    frr_cfg.modify_section(r'^bgp as-path access-list .*')
+    frr_cfg.modify_section(r'^bgp community-list .*')
+    frr_cfg.modify_section(r'^bgp extcommunity-list .*')
+    frr_cfg.modify_section(r'^bgp large-community-list .*')
+    frr_cfg.add_before('^line vty', policy['new_frr_config'])
+    frr_cfg.commit_configuration(bgp_daemon)
+
+    # The route-map used for the FIB (zebra) is part of the zebra daemon
+    frr_cfg.load_configuration(zebra_daemon)
+    frr_cfg.modify_section(r'^access-list .*')
+    frr_cfg.modify_section(r'^ipv6 access-list .*')
+    frr_cfg.modify_section(r'^ip prefix-list .*')
+    frr_cfg.modify_section(r'^ipv6 prefix-list .*')
+    frr_cfg.add_before('^line vty', policy['new_frr_config'])
+    frr_cfg.commit_configuration(zebra_daemon)
 
     # If FRR config is blank, rerun the blank commit x times due to frr-reload
     # behavior/bug not properly clearing out on one commit.
-   # if policy['new_frr_config'] == '':
-   #     for a in range(5):
-   #         frr_cfg.commit_configuration(frr_daemon)
+    if policy['new_frr_config'] == '':
+        for a in range(5):
+            frr_cfg.commit_configuration(zebra_daemon)
 
+    # Save configuration to /run/frr/config/frr.conf
+    frr.save_configuration()
 
     return None
 
-- 
cgit v1.2.3