Policy package version 3

Yang

module policy {
  yang-version 1.1;
  namespace "http://example.com/policy";
  prefix policy;

  import tailf-common { prefix tailf; }
  import tailf-ncs { prefix ncs; }
  import ietf-inet-types { prefix inet; } 

  description "Per-device prefix-set + route-policy using leaf-list for batch updates.";
  revision 2025-07-18 { description "Refactored to leaf-list for high volume scalability"; }

  list policy {
    key name;
    uses ncs:service-data;
    ncs:servicepoint "policy-servicepoint";

    leaf name { type string; }

    list device {
      key device-name;
      min-elements 1;

      leaf device-name {
        type leafref { path "/ncs:devices/ncs:device/ncs:name"; }
      }

      list prefix-set {
        key name;
        leaf name { type string; }

        leaf-list prefixes {
          type inet:ipv4-prefix;
          tailf:info "List of IPv4 prefixes (e.g., 10.0.0.0/24)";
        }
      }

      list route-policy {
        key name;
        min-elements 1;
        description "At least one route-policy required per device.";

        leaf name { type string; }

        leaf prefix-set-ref {
          type leafref { path "../../prefix-set/name"; }
          mandatory true;
        }
      }
    }
  }
}

python

# -*- mode: python; python-indent: 4 -*-
import ncs
from ncs.application import Service, Application

class ServiceCallbacks(Service):
    @Service.create
    def cb_create(self, tctx, root, service, proplist):
        self.log.info(f"Service create: {service.name}")
        tmpl = ncs.template.Template(service)

        for dev in service.device:
            dev_name = dev.device_name

            for ps in dev.prefix_set:
                ps_name = ps.name
                
                for prefix in ps.prefixes:
                    vars_ps = ncs.template.Variables()
                    vars_ps.add('DEVICE_NAME', dev_name)
                    vars_ps.add('PS_NAME', ps_name)
                    vars_ps.add('PREFIX', prefix)
                    tmpl.apply('prefix-set-template', vars_ps)

            for rp in dev.route_policy:
                ps_name = rp.prefix_set_ref
                
                definition = f"""if destination in {ps_name} then
  pass
else
  drop
endif"""
                vars_rp = ncs.template.Variables()
                vars_rp.add('DEVICE_NAME', dev_name)
                vars_rp.add('RP_NAME', rp.name)
                vars_rp.add('DEFINITION', definition)
                tmpl.apply('route-policy-template', vars_rp)

class Policy(Application):
    def setup(self):
        self.log.info('Policy package RUNNING')
        self.register_service('policy-servicepoint', ServiceCallbacks)
    def teardown(self):
        self.log.info('Policy package STOPPING')

template

prefix-set-template.xml

<config-template xmlns="http://tail-f.com/ns/config/1.0">
  <devices xmlns="http://tail-f.com/ns/ncs">
    <device>
      <name>{$DEVICE_NAME}</name>
      <config>
        <prefix-set xmlns="http://tail-f.com/ned/cisco-ios-xr">
          <name>{$PS_NAME}</name>
          <set>
            <value>{$PREFIX}</value>
          </set>
        </prefix-set>
      </config>
    </device>
  </devices>
</config-template>

route-policy-template.xml

<config-template xmlns="http://tail-f.com/ns/config/1.0">
  <devices xmlns="http://tail-f.com/ns/ncs">
    <device>
      <name>{$DEVICE_NAME}</name>
      <config>
        <route-policy xmlns="http://tail-f.com/ned/cisco-ios-xr">
          <name>{$RP_NAME}</name>
          <value>{$DEFINITION}</value>
        </route-policy>
      </config>
    </device>
  </devices>
</config-template>