PY-SIL Callback Reference

Note

  • This section is preliminary.

  • More examples and callbacks will be added in the future.

Each callback type has its own callback API function prototype. To register callback it should be added to a specific y_[module_name].py file in "callbacks" section (see PY-SIL Callback Definition in the Config).

This code is usually generated automatically by pyang library plugin. Refer to PY-SIL Code Generation for examples on how auto generate stub PY-SIL code.

The following APIs are described in this section:

PY-SIL GET2 Callback

A GET2 callback method is usually mapped to a single data node. The server will issue “get” and “getnext” requests via this API, and the callback code will return value nodes for the data it is managing. These data nodes are not maintained in the server Data Tree. They are not cached in the server.

This API uses 1 callback per object template. The server will issue “get” and “getnext” requests via the API, and the callback code will return value nodes for the data is is managing. These data nodes are not maintained in the server data tree. They are not cached in the server.

Which Nodes Have PY-SIL GET2 Callbacks

This section is described in the YumaPro Developer Manual. For the information please see Which Nodes Have GET2 Callbacks.

PY-SIL GET2 Callback Method

def get2_method_name(self, silcb: SILCbGet)

Callback method for server object handler get2 callback Used to provide main and/or subsystem retrieval of instances of a specific named object.

Parameters:

silcb (SILCbGet) -- GET2 control block contains all the input, output, and state data used for GET2 transactions.

Returns:

STATUS_T.NO_ERR if executed OK and found OK STATUS_T.ERR_NCX_NO_INSTANCE warning if no instance found

Return type:

STATUS_T

PY-SIL GET2 Control Block

class pysil.module.SILCbGet(scb: LP_STRUCT_SES_CB_T_, msg: LP_STRUCT_RPC_MSG_T_, get2cb: LP_STRUCT_GETCB_GET2_T_, module_name: str, callback_info: dict)

Bases: SILCbBase

The SILCbGet class is a comprehensive control block used in GET2 callbacks. It encapsulates all necessary pointers and information required for the execution of the GET2 callback methods.

add_return_data(data: list | dict | str | int, has_more: bool = False) ENUM_STATUS_T_

Adds data to return

Parameters:
  • data (dict, list, str or int) -- provide return data here list (for list of objects), dict (for single object), str or int (for simple leaf nodes)

  • has_more (bool) -- set to True if has more data to return

Returns:

Status

property ancestor_keys: dict

Ancestors keys for silcb

property callback_info: dict

Property to get callback_info

property cbmode: int

Reason for the callback (get or getnext), compare with GETCB_MODE_T values

property choice_active_case: str

FOR CHOICES ONLY! Current active case

property client_addr: str

This field contains the client address involved in the transaction. or None

property get2cb: LP_STRUCT_GETCB_GET2_T_

Contains a pointer to the session control block that initiated the request, representing the current session's context.

property local_keys: dict

Local keys for silcb

property local_keys_fixed: list

Local keys fixed for silcb

property local_keys_present: list

Local keys present for silcb

log(msg: str, log_level: str = 'INFO') None

Adds message to log

property module_name: str
property msg: LP_STRUCT_RPC_MSG_T_

Pointer to incoming XML message header

scb() LP_STRUCT_SES_CB_T_

Pointer to scb

send_terminal_message(message: str) ENUM_STATUS_T_

Sends message to terminal

set_choice_active_case(active_case: str) ENUM_STATUS_T_

FOR CHOICES ONLY! Sets active_case to specific value for choices.

Parameters:

active_case (str) -- Provide active case string here

Returns:

Status

property username: str

user_id value or None

property utils: PYSILUtils

returns utils for the silcb

PY-SIL GET2 Return Data

Each GET2 method returns a status code:

  • NO_ERR: data is returned

  • ERR_NCX_NO_INSTANCE: the specified data instance does not exist

  • Other status code: some error occurred

If the node has child nodes the callback is expected to add data using add_return_data() method of SILCbGet. Depending on the object type and object nodes the data format will be different.

PY-SIL GET2 Return Data Examples

This section briefly illustrates examples of common nodes returning data within PY-SIL GET2 callback.

container - returns the child terminal nodes values.

# collect return_data
return_data = {
    "B1": None,  # remove or replace value with correct <int>
}
# add_return data
res = silcb.add_return_data(data=return_data)

list - Returns the keys values and perhaps the rest of the child terminal nodes values. Set “has_more” in add_return_data() to trigger “getnext” until no more instances found.

# collect return_data
return_data = {
    "last-name": "str",  # replace value with correct <str>
    "first-name": "str",  # replace value with correct <str>
    "street": "str",  # replace value with correct <str>
    "city": "str",  # replace value with correct <str>
    "zipcode": "str",  # replace value with correct <str>
}
# set has_more=True if more data available
# add return data
res = silcb.add_return_data(data=return_data, has_more=True)

choice - Returns the name of the active case and child terminal nodes values in the active case.

# set choice active case
silcb.set_choice_active_case("A")
# collect return_data
return_data = {
  # Choose one of:
  # * Case A
  "A1": "str",  # remove or replace value with correct <str>
  "A2": "str",  # remove or replace value with correct <str>
  # * Case B
  # "A3": "int",  # remove or replace value with correct <int>
  # # No data to return
  # * Case C
}
# add return data
res = silcb.add_return_data(data=return_data)

leaf - Returns the leaf value.

# set return data
return_data = "str"  # *required - replace value with
# add return data
res = silcb.add_return_data(data=return_data)

leaf-list - Returns all values for the leaf-list.

# collect return_data
return_data = [
  53,
  67,
  92
]
# add return data
res = silcb.add_return_data(data=return_data)

PY-SIL GET2 Processing Procedure

The server will send a <get-request> server request message to each subsystem that has registered a get2 callback for the specific object.

  • For single-instance nodes (container, leaf, choice, anyxml) only a “get” request will be made.

  • For a leaf-list node, only “get” requests will be made, but all instances of the leaf-list are returned at once.

  • For a list node, a “get” request will be made if the keys are known.

  • A “get” request that contains no keys will cause the first instance to be returned.

  • Entries are ordered by their key values, exactly as specified in the YANG list. If there is no key-stmt defined, then the callback will still return the first entry, except the server will not attempt any sorting if multiple entries are returned (E.g 1 from each subsystem).

  • It is always up to the callback to know what is the “best” next entry, given the keys that are returned.

  • If there are more list entries, then the has_more” parameter in add_return_data() should be set to True.

  • The server will issue “getnext” (GETCB_GETNEXT_VALUE) requests as needed until the “has_more” parameter is set to False.

PY-SIL GET2 Callback Initialization and Cleanup

PY-SIL library provides functionality to register callback methods based on a configuration provided in a dictionary format. It register all callback specified in the configuration during init phase 1 and unregister them during cleanup phase.

config = {
    callbacks: [
        {
        "path": "/address_get_2:addresses/address",
        "cb_get2": "get_address",
        }
    ]
}
  • "path": "/address_get_2:addresses/address" This specifies the path where the callback method get_address should be invoked.

  • "cb_get2": "get_address" This specifies the name of the callback method that should be invoked when a request is made to the specified path. In this case, the method named get_address will be invoked.

PY-SIL GET2 Callback Examples

PY-SIL GET2 Callback Container Example

Consider this simplified example, that represents GET2 callback for the “interface-state” container.

container interfaces-state {
  config false;

  leaf in-errors { type uint32; }

  // rest of leafs removed
}

The following PY-SIL code shows a GET2 callback which returns the “interfaces-state” container terminal node value.

class PYSILModuleIetfInterfaces(PYSILModuleIetfInterfacesBase):
    # ...
    def get_interfaces_state(self, silcb: SILCbGet):
        """
        Get database object callback for container interfaces-state
        Path: container interfaces-state
        Fill in 'get2cb' response fields.

        Parameters
        ----------
            silcb:SILCbGet
                Control block

        Returns
        -------
            STATUS_T
                status of the callback.
        """
        self.log(f"Enter 'get_interfaces_state' callback")

        if silcb.cbmode == GETCB_MODE_T.GETCB_GET_VALUE:
            pass
        elif silcb.cbmode == GETCB_MODE_T.GETCB_GETNEXT_VALUE:
            return STATUS_T.ERR_NCX_NO_INSTANCE
        else:
            # USE SET_ERROR FOR PROGRAMMING BUGS ONLY
            return self.flag_internal_error()

        res = STATUS_T.NO_ERR

        # Item found. Create a python object (dict, list, etc) with item data
        return_data = {
            "in-errors": 8,  # remove or replace value with correct <int>
        }

        # Save return data
        res = silcb.add_return_data(data=return_data)

        # Return status
        return res

To ensure that the GET2 callbacks are working as expected an application may retrieve running configuration and device state information as follows:

<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <get>
    <filter type="subtree">
      <interfaces-state xmlns="http://yumaworks.com/ns/ietf-interfaces-example" />
    </filter>
  </get>
</rpc>

The server may reply with:

<rpc-reply message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <interfaces-state xmlns="http://yumaworks.com/ns/ietf-interfaces-example">
     <in-errors>8</in-errors>
   </interfaces-state>
</rpc-reply>

PY-SIL GET2 Callback List Example

Consider this simplified example, that demonstrates a GET2 callback for the “interface” list.

container interfaces-state {
  config false;

  list interface {
    key "ip";

    leaf ip {
      type inet:ip-prefix;
    }

    leaf name {
      type string;
      mandatory true;
    }
  }
}

The GET2 callbacks for “list” elements must fill in the return key values. All additional leafs are optional to fill in. Any other descendant complex elements, such other lists, containers, or choice nodes must have their own callbacks methods.

class PYSILModuleIetfInterfaces(PYSILModuleIetfInterfacesBase):
    # ...
    def get_list_interface(self, silcb: SILCbGet):
        """
        Get database object callback
        Path: list /interfaces-state/interface

        Parameters
        ----------
            silcb:SILCbGet
                Control block

        Returns
        -------
            STATUS_T
                status of the callback.

        Additional
        ----------
            silcb.ancestor_keys: dict with ancestor keys
                no ancestor keys
            silcb.local_keys: dict with local keys
                silcb.local_keys["ip"]: str
                    Local key leaf 'ip' in list 'interface' Path: /interfaces-state/interface/ip
            silcb.local_keys_present: list of present and valid local keys
            silcb.local_keys_fixed: list of local keys fixed in a getnext request

        """

        data = ["10.10.10.1/16", "10.10.10.2/16", "10.10.10.3/16"]
        ip, has_more = self.__get2_from_list(silcb, key_name="ip", data=data)

        if ip is None:
            return STATUS_T.ERR_NCX_NO_INSTANCE

        # Item found. Create a python object (dict, list, etc) with item data
        return_data = {
            "ip": ip,  # *required - replace value with correct <str>
            "name": "interface-name",  # remove or replace value with correct <str>
        }

        # Save return data and mark if next item exists
        res = silcb.add_return_data(data=return_data, has_more=has_more)

        # Return status
        return res


    def __get2_from_list(self, silcb: SILCbGet, key_name: str, data: list):

        data_found = False
        has_more = False

        name = silcb.local_keys[key_name]
        ret_name = None

        if silcb.cbmode == GETCB_MODE_T.GETCB_GET_VALUE:
            if name:
                # /* get a specified instance */
                if name in data:
                    ret_name = name
                    has_more = name != data[-1]
                    data_found = True
                else:
                    return None, has_more
            else:
                # // else no keys == get-first
                ret_name = data[0]
                data_found = True
                has_more = True, has_more

        # If the callback mode is GETCB_GETNEXT_VALUE (get next value) Find current item first (by keys values from current node) and get next item to return
        if silcb.cbmode == GETCB_MODE_T.GETCB_GETNEXT_VALUE:
            # use keys silcb.local_keys to filter data and find next record
            if name in silcb.local_keys_fixed:
                return None, has_more

            # /* adjust the key to find the next entry after
            # * the specified value
            # */
            if not name:
                ret_name = data[0]  # return first entry [0]
                has_more = True
                data_found = True
            else:
                # /* find the correct entry to retrieve */
                for i in range(len(data) - 1):
                    if name == data[i]:
                        ret_name = data[i + 1]
                        has_more = i < len(data) - 1
                        data_found = True
                        break

        # If item doesn't exist, exit with an error
        if not data_found:
            return None, has_more

        # Item found. Create a python object (dict, list, etc) with item data
        return ret_name, has_more

To ensure that the GET2 callbacks are working as expected an application can retrieve running configuration and device state information as follows:

<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <get>
    <filter type="subtree">
      <interfaces-state xmlns="http://yumaworks.com/ns/ietf-interfaces-example">
        <interface>
          <ip>10.10.10.1/16</ip>
            </interface>
      </interfaces-state>
    </filter>
  </get>
</rpc>

The server may reply with:

<rpc-reply message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <interfaces-state xmlns="http://yumaworks.com/ns/ietf-interfaces-example">
    <interface>
      <ip>10.10.10.1/16</ip>
      <name>interface-name</name>
    </interface>
  </interfaces-state>
</rpc-reply>

If no key value is specified in the request or the request subtree filter target is “interface-state” parent container, then the server will try to output all the interfaces and its children:

<?xml version="1.0" encoding="UTF-8"?>
 <rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
   <get>
     <filter type="subtree">
       <interfaces-state xmlns="http://yumaworks.com/ns/ietf-interfaces-example" />
     </filter>
   </get>
 </rpc>

The server may reply with:

<rpc-reply message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <interfaces-state xmlns="http://yumaworks.com/ns/ietf-interfaces-example">
    <interface>
      <ip>10.10.10.1/16</ip>
      <name>interface-name</name>
    </interface>
    <interface>
      <ip>10.10.10.2/16</ip>
      <name>interface-name2</name>
    </interface>
    <interface>
      <ip>10.10.10.3/16</ip>
      <name>interface-name3</name>
    </interface>
  </interfaces-state>
</rpc-reply>

PY-SIL GET2 Callback Choice Example

A GET2 callback method for a YANG Choice is expected to set the name of the active choice (silcb.set_choice_active_case(...)), and any terminal nodes values from the active case. It can also return ERR_NCX_NO_INSTANCE if no active case is set.

Consider this simplified example, that represents GET callback for the node type “choice”.

choice type {
   case interface {          // active case
     leaf interface {
       type if:interface-ref;
     }
   }

   case case-network {
     leaf next-hop-host {
       type inet:ip-address;
     }
   }
 }

The following python code shows a simple GET2 callback method for the data model, which returns the name of the active case and child terminal nodes in the active case when it is requested.

class PYSILModuleIetfInterfaces(PYSILModuleIetfInterfacesBase):
    # ...
    def get_type(self, silcb: SILCbGet):
        """
        Get database object callback
        Path: choice /type

        Parameters
        ----------
            silcb:SILCbGet
                Control block

        Returns
        -------
            STATUS_T
                status of the callback.

        """
        self.log(f"Enter 'get_type' callback get2-test@2022-03-02")

        res = STATUS_T.NO_ERR
        data_found = False

        # get the active case
        # It may be NULL if no active case and choice is optional
        active_case = "interface"
        # set the active case return values
        if active_case:
            res = silcb.set_choice_active_case(active_case)
            if res != STATUS_T.NO_ERR:
                return res

            return_data =     {
                # Choose one of:
                # * Case interface
                "interface": "if:interface-ref",  # remove or replace value with correct <if:interface-ref>
                # * Case case-network
                # "next-hop-host": "inet:ip-address",  # remove or replace value with correct <inet:ip-address>
            }

            # Save return data
            res = silcb.add_return_data(data=return_data)

        # Return status
        return res

Note that any leaf or leaf-list nodes that are in nested within other “choice” statements within the active case are not returned. These are considered complex objects and the caller will ask for nested choice, list, and container nodes in a separate call.

container top {
  choice complex-type {
     case interface {          // active case
       choice new-choice {
         leaf interface {
           type if:interface-ref;
         }
         leaf test-interface {
           type if:interface-ref;
         }
       }
     }

     case case-network {
       leaf next-hop-host {
         type inet:ip-address;
       }
     }
   }
 }

In the example above, the GET2 callback for 'complex-choice' would be called first and the active case name interface would be returned without a terminal node.

Then the GET2 callback for 'new-choice' would be invoked and this callback will return the name of the active case (interface or test-interface), and the child leaf for that case.

GET2 Callback Example for To ensure that the GET2 callback is working as expected an application can retrieve running configuration and device state information as follows:

<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <get>
    <filter type="subtree">
      <top xmlns="http://yumaworks.com/ns/interfaces" />
    </filter>
  </get>
</rpc>

The server may reply with:

<rpc-reply message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <top xmlns="http://yumaworks.com/ns/interfaces">
    <interface>ethernet1/1/1</interface>
  </top>
</rpc-reply>

PY-SIL GET2 Callback Leaf Example

Consider this simplified example, that represents GET callback for a “leaf” node.

container top {
  leaf get2-leaf {
    config false;
    type int32;
  }
}

Note that only leaf or leaf-list nodes that are top level nodes or the parent node is a config=true node will require a GET2 callback.

The following python code shows a simple GET2 callback method for the data model, which returns the leaf value when it is requested.

class PYSILModuleIetfInterfaces(PYSILModuleIetfInterfacesBase):
    leaf_val = 0
    # ...
    def get_get2_leaf(self, silcb: SILCbGet):
      """
      Get database object callback
      Path: leaf /get2test:get2-leaf

      Parameters
      ----------
          silcb:SILCbGet
              Control block

      Returns
      -------
          STATUS_T
              status of the callback.

      """
      self.log(
          f"Enter 'get_get2_leaf' callback get2-test@2022-03-02 ancestor_keys={silcb.ancestor_keys} local_keys={silcb.local_keys} silcb.local_keys_present={silcb.local_keys_present} silcb.local_keys_fixed={silcb.local_keys_fixed}"
      )

      has_more = False

      # Check callback mode (silcb.cbmode) to find data required to return
      # If the callback mode is GETCB_GET_VALUE (get first value), use first record from your filtered data
      if silcb.cbmode == GETCB_MODE_T.GETCB_GET_VALUE:
          # get the real value from the system here.
          # But in this example just increment the value by 1
          self.leaf_val += 1

      # If the callback mode is GETCB_GETNEXT_VALUE (get next value) Find current item first (by keys values from current node) and get next item to return
      if silcb.cbmode == GETCB_MODE_T.GETCB_GETNEXT_VALUE:
          # use keys silcb.local_keys to filter data and find next record
          return STATUS_T.ERR_NCX_NO_INSTANCE

      # Item found. Set specific return_data value
      return_data = self.leaf_val  # remove or replace value with correct <int>

      # Save return data and mark if next item exists
      res = silcb.add_return_data(data=return_data, has_more=has_more)

      # Return status
      return res

In the above example, the code generates the value based on the static value that is incremented after each retrieval. An agent can generate the value based on the real device statistical data or based on the system's statistical data, etc.

To ensure that the GET2 callback is working as expected an application can retrieve running configuration and device state information as follows:

<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <get>
    <filter type="subtree">
      <get2-leaf xmlns="http://yumaworks.com/ns/interfaces" />
    </filter>
  </get>
</rpc>

The server may reply with:

<rpc-reply message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
   <get2-leaf xmlns="http://yumaworks.com/ns/interfaces">43</get2-leaf>
</rpc-reply>

PY-SIL GET2 Callback Leaf-List Example

A GET2 callback for a leaf-list is similar to the callback for a leaf, except multiple values can be returned, instead of one value. The server will return the data in the same order as returned by the callback method.

Consider this simplified example, that represents a GET2 callback for a “leaf-list” node.

leaf-list get2-leaf-list {
  config false;
  type int32;
}

The following python code shows a simple GET2 callback method for the same data model, which returns the leaf-list values when requested.

class PYSILModuleIetfInterfaces(PYSILModuleIetfInterfacesBase):
    # ...
    def get_get2_leaf_list(self, silcb: SILCbGet):
        """
        Get database object callback
        Path: leaf-list /get2test:get2-leaf-list

        Parameters
        ----------
            silcb:SILCbGet
                Control block

        Returns
        -------
            STATUS_T
                status of the callback.
        """
        self.log(
            f"Enter 'get_get2_leaf_list' callback get2-test@2022-03-02 ancestor_keys={silcb.ancestor_keys} local_keys={silcb.local_keys} silcb.local_keys_present={silcb.local_keys_present} silcb.local_keys_fixed={silcb.local_keys_fixed}"
        )

        data_found = False
        has_more = False
        # Check callback mode (silcb.cbmode) to find data required to return
        # If the callback mode is GETCB_GET_VALUE (get first value), use first record from your filtered data
        if silcb.cbmode == GETCB_MODE_T.GETCB_GET_VALUE:
            data_found = True
            # 3 entries [53, 67, 92]
            return_data = [53, 67, 92]
            pass

        # If the callback mode is GETCB_GETNEXT_VALUE (get next value) Find current item first (by keys values from current node) and get next item to return
        if silcb.cbmode == GETCB_MODE_T.GETCB_GETNEXT_VALUE:
            return STATUS_T.ERR_NCX_NO_INSTANCE

        # If item doesn't exist, exit with an error
        # check if the leaf-list exists;
        # determined by the SIL or SIL-SA
        # callback based on instances in the system
        data_found = True
        if not data_found:
            return STATUS_T.ERR_NCX_NO_INSTANCE

        # Save return data and mark if next item exists
        res = silcb.add_return_data(data=return_data, has_more=has_more)

        # Return status
        return res

In the above example, the code generates the value based on the static value that does not fluctuate after each retrieval. An agent can generate the value based on the real device statistical data or based on the system's statistical data, etc.

To ensure that the GET2 callback is working as expected an application can retrieve running configuration and device state information as follows:

<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <get>
    <filter type="subtree">
      <get2-leaf-list xmlns="http://yumaworks.com/ns/interfaces" />
    </filter>
  </get>
</rpc>

The server may reply with:

<rpc-reply message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <get2-leaf-list xmlns="http://yumaworks.com/ns/interfaces">53</get2-leaf-list>
  <get2-leaf-list xmlns="http://yumaworks.com/ns/interfaces">67</get2-leaf-list>
  <get2-leaf-list xmlns="http://yumaworks.com/ns/interfaces">92</get2-leaf-list>
</rpc-reply>

PY-SIL All In One (AIO) GET2 Callback

This section describes All In One (AIO) GET2 mechanism that allows to call a single GET2 callback for the whole Subtree. Common information about All In One GET2 Callback are available in All In One GET2 Callback.

PY-SIL supports All In One GET2 Callback returns in JSON format only.

PY-SIL GET2 AIO Callback Control Block

Control Block for PY-SIL GET2 AIO Callback is the sames as PY-SIL GET2 Control Block.

PY-SIL AIO List Example

In this example the server will invoke GET2 callbacks only for "nonconfig-list" first and then for AIO "nonconfig-list2" list, and will not invoke any deeper level callbacks.

The server will expect that the AIO "nonconfig-list2" callback will fill in the GET2 control block with the sufficient information about "nonconfig-list2" node children, complex children as well as terminal nodes.

Example Python file:

  • /usr/share/yumapro/src/address_get_aio/u_address_get_aio.py

This section illustrates how to use All In One GET2 callbacks for list nodes. Assume we have the same data model as described above then the GET2 callback may look as follow:

class PYSILModuleAddressGetAio(PYSILModuleAddressGetAioBase):
    # ...
    def get_address(self, silcb: SILCbGet):
        """
        Get database object callback for list "address"
        Path: list /address_get_aio:addresses/address

        Parameters
        ----------
            silcb: SILCbGet
                Control block

        Returns
        -------
            STATUS_T
                status of the callback.

        Additional
        ----------
            silcb.local_keys: dict with local keys
                silcb.local_keys["last-name"]: str
                    Local key leaf 'last-name' in list 'address' Path: /address_get_aio:addresses/address/last-name
                silcb.local_keys["first-name"]: str
                    Local key leaf 'first-name' in list 'address' Path: /address_get_aio:addresses/address/first-name
            silcb.local_keys_present: list of present and valid local keys
            silcb.local_keys_fixed: list of local keys fixed in a getnext request
        """

        self.log(
            f"Enter 'get_address' callback address_get_aio@2024-01-22 local_keys={silcb.local_keys} silcb.local_keys_present={silcb.local_keys_present} silcb.local_keys_fixed={silcb.local_keys_fixed}"
        )

        # Load data from YAML file
        test_data = load_test_data()

        res = STATUS_T.NO_ERR
        data_found = False

        # Apply filters by local and ancestor keys if needed
        # Filter data by silcb.local_keys_present and silcb.local_keys_fixed keys from silcb.local_keys
        if "last-name" in silcb.local_keys_fixed:
            # Local key leaf "last-name" in list "address" Path: /address_get_aio:addresses/address/last-name
            # apply filter for the key using silcb.local_keys["last-name"] value
            test_data = [
                r
                for r in test_data
                if r.get("last-name") == silcb.local_keys["last-name"]
            ]

        if "first-name" in silcb.local_keys_fixed:
            # Local key leaf "first-name" in list "address" Path: /address_get_aio:addresses/address/first-name
            # apply filter for the key using silcb.local_keys["first-name"] value
            test_data = [
                r
                for r in test_data
                if r.get("first-name") == silcb.local_keys["first-name"]
            ]

        # Check callback mode (silcb.cbmode) to find data required to return
        # If the callback mode is GETCB_GET_VALUE (get first value), use first record from your filtered data
        if silcb.cbmode == GETCB_MODE_T.GETCB_GET_VALUE:
            pass

        # If the callback mode is GETCB_GETNEXT_VALUE (get next value) Find current item first (by keys values from current node) and get next item to return
        elif silcb.cbmode == GETCB_MODE_T.GETCB_GETNEXT_VALUE:
            return STATUS_T.ERR_NCX_NO_INSTANCE
        else:
            return self.flag_internal_error()

        # If item doesn't exist, exit with an error
        data_found = len(test_data) > 0
        if not data_found:
            return STATUS_T.ERR_NCX_NO_INSTANCE

        # Items found. Create a python list of objects (dicts) with items data
        # Example for data with one item in the list
        # return_data = [
        #     # One item example
        #     {
        #         "last-name": "str",  # *required - replace value with correct <str>
        #         "first-name": "str",  # *required - replace value with correct <str>
        #         "street": "str",  # remove or replace value with correct <str>
        #         "city": "str",  # remove or replace value with correct <str>
        #         "zipcode": "str",  # remove or replace value with correct <str>
        #         "phone": {
        #             "phone-type": "str",  # *required - replace value with correct <str>
        #             "phone-number": "str",  # *required - replace value with correct <str>
        #         },
        #     },
        # ]
        return_data = test_data

        # Save return data
        res = silcb.add_return_data(data=return_data)

        # Return status
        return res

PY-SIL EDIT3 Callback

The PY-SIL supports only EDIT3 callback for edit operations (EDIT1 and EDIT2 are not supported in PY-SIL).

The EDIT3 callback provides a more streamlined and efficient approach to handling YANG model-driven configurations. The key feature of EDIT3 in PY-SIL is its utilization of the 'update_json' value, which combines new user inputs with existing data, simplifying the process of modifying configuration data nodes.

Key Features of EDIT3 in PY-SIL:

  • Enhanced Data Management: EDIT3 callbacks facilitate more effective management of configuration data by consolidating new and existing values into a single 'update_json' structure. This approach reduces complexity and potential errors in processing configuration changes.

  • Efficiency in Processing: The integration of the 'update_json' value in EDIT3 callbacks minimizes the need for separate handling of new and current values, enabling more efficient processing of configuration changes.

  • Streamlined Callback Implementation: By eliminating the need for 'child_edit' and reducing redundancy, EDIT3 in PY-SIL provides a cleaner and more concise callback implementation compared to previous callback generations.

Common information about EDIT3 related to the server is available in EDIT3 Callback.

PY-SIL EDIT3 Callback Method

def edit_method_name(self, silcb: SILCbEdit)

Used to provide a callback sub-mode for a specific named complex object

Parameters:

silcb (SILCbEdit) -- Edit control block that contains full EDIT3 callback information

Returns:

status

Return type:

STATUS_T

PY-SIL EDIT3 Control Block

class pysil.module.SILCbEdit(editcb: LP_STRUCT_AGT_EDITCB_T_, module_name: str, callback_info: dict)

Bases: SILCbBase

The SILCbEdit class is a comprehensive control block used in EDIT3 callbacks. It encapsulates all necessary pointers and information required for the execution of the EDIT3 callback methods.

property ancestor_keys: dict

Ancestors keys for silcb

property callback_info: dict

Property to get callback_info

property cbtyp: c_uint

Indicates the reason for the callback, which could be one of several types from AGT_CBTYP_T, such as:

  • AGT_CB_VALIDATE

  • AGT_CB_APPLY

  • AGT_CB_COMMIT

  • AGT_CB_ROLLBACK

property client_addr: str

This field contains the client address involved in the transaction. or None

property curval: LP_STRUCT_VAL_VALUE_T_

This field refers to the current container/list values from either the <running> or <candidate> configuration, which could be None for certain operations like create.

property curval_json: dict

This field refers to the current container/list values from either the <running> or <candidate> configuration, which could be None for certain operations like create.

property editop: c_ubyte

This enumerator represents the parent edit-config operation type, affecting how the callback operates on objects. Possible types from OP_EDITOP_T include:

  • OP_EDITOP_LOAD

  • OP_EDITOP_MERGE

  • OP_EDITOP_REPLACE

  • OP_EDITOP_CREATE

  • OP_EDITOP_DELETE

property local_keys: dict

Local keys for silcb

log(msg: str, log_level: str = 'INFO') None

Adds message to log

property module_name: str
property msg: LP_STRUCT_RPC_MSG_T_

This pointer refers to the incoming RPC message in progress, detailing the specific request being processed.

property msg_id: str

txid_str value or None

property newval: LP_STRUCT_VAL_VALUE_T_

This field contains object that holds the proposed changes to be applied to the current configuration, varying based on the editop value.

property newval_json: dict

This field contains object that holds the proposed changes to be applied to the current configuration, varying based on the editop value.

property scb: LP_STRUCT_SES_CB_T_

This field contains a pointer to the session control block that initiated the request, representing the current session's context.

send_terminal_message(message: str) ENUM_STATUS_T_

Sends message to terminal

property update: LP_STRUCT_VAL_VALUE_T_

This field contains a merged value of all new and current values from the datastore, streamlining the update process.

property update_json: dict

This field contains a merged value of all new and current values from the datastore, streamlining the update process.

property username: str

user_id value or None

property utils: PYSILUtils

returns utils for the silcb

This control block is pivotal in managing the intricacies of EDIT3 callbacks, ensuring that all necessary data and context are readily available for efficient processing.

PY-SIL EDIT3 Callback Initialization and Cleanup

PY-SIL library provides functionality to register callback methods based on a configuration provided in a dictionary format. It register all callback specified in configuration during init phase 1 and unregister them during cleanup phase.

Example 1

config = {
    # ...
    callbacks: [
        # ...
        {"path": "/if:interfaces", "cb_edit3": "edit_interfaces"},
    ]
}
  • "path": "/if:interfaces" This specifies the path where the callback method "edit_interfaces" should be invoked.

  • "cb_edit3": "edit_interfaces" This specifies the name of the callback method that should be invoked when a request is made to the specified path. In this case, the method named "edit_interfaces" will be invoked.

Example 2

config = {
    # ...
    callbacks: [
        # ...
        {
            "path": "/if:interfaces/interface",
            "cb_edit3": "edit_interface",
            "lvl": 3,
            "keys": [
                {"name": "name", "pname": "name", "type": "string", "lvl": 3}
            ],
        },
    ]
}
  • "path": "/if:interfaces/interface" This specifies the path where the callback method "edit_interface" should be invoked.

  • "cb_edit3": "edit_interface" This specifies the name of the callback method that should be invoked when a request is made to the specified path. In this case, the method named "edit_interface" will be invoked.

  • "lvl": 3 This specifies the level in the node's tree for the callback.

  • "keys": [] This specifies list of keys (ancestor keys and local keys) for the callback. Each key has:

    • name - yang name for a node.

    • pname - python parameter name in keys list.

    • type - type of the key.

    • lvl - level in the node's tree for the key node.

PY-SIL EDIT3 Callback Example

An example implementation of an EDIT3 callback method demonstrates how the 'update_json' value is utilized in various editing operations.

The following code snippet provides an example of how to implement the EDIT3 callback in a practical scenario, specifically focusing on managing interface entries within a network device configuration context.

The edit3_interface callback serves as the main EDIT3 callback handler for interface edits. It is structured to manage different phases of the callback process, aligning actions with the specified operation type (editop).

Key functionalities of this callback include:

  • Callback Information Handling: The callback handling begins by extracting relevant information from the editcb control block

  • Handling Different Callback Types: Based on the cbtyp, the callback executes different actions. For example, in the AGT_CB_COMMIT phase, it determines whether to invoke delete_interface or update_interface based on whether the edit operation is a delete or an update.

  • Logging and Error Handling: The callback includes provisions for logging and error handling, ensuring that each action taken is properly recorded and any issues are promptly addressed.

This example illustrates the practical application of the EDIT3 callback in a network configuration context. It showcases how the update value can be used to efficiently manage changes to the device’s configuration, either by updating existing entries or deleting them as required.

class PYSILModuleIetfInterfaces(PYSILModuleIetfInterfacesBase):
    # ...
    def edit_interface(self, silcb: SILCbEdit):
        """
        Edit database object callback
        Path: list /if:interfaces/interface

        Parameters
        ----------
            silcb:SILCbEdit
                Control block

        Returns
        -------
            STATUS_T
                Return status for the phase.

        Additional
        ----------
            silcb.local_keys: dict with local keys
                silcb.local_keys["name"]: str
                    Local key leaf 'name' in list 'interface' Path: /if:interfaces/interface/name
        """


        
        self.log(f"Enter 'edit3_interface' callback ietf-interfaces@2018-02-20 for {AGT_CBTYP_T(silcb.cbtyp).name} phase silcb.editop={OP_EDITOP_T(silcb.editop).name} local_keys={silcb.local_keys})
                
        res = STATUS_T.NO_ERR

        if silcb.cbtyp == AGT_CBTYP_T.AGT_CB_VALIDATE:
            # description-stmt validation here
            pass
        elif silcb.cbtyp == AGT_CBTYP_T.AGT_CB_APPLY:
            # database manipulation done here
            pass
        elif silcb.cbtyp == AGT_CBTYP_T.AGT_CB_COMMIT:
            # device instrumentation done here
            if silcb.editop == OP_EDITOP_T.OP_EDITOP_DELETE:
                # Delete all data from the device
                self.log(f"Delete entry")
                res = delete_interface(silcb.local_keys["name"])
            else:
                # Use silcb.update_json to update your device data
                # 
                # The technique here is to replace the existing data
                # with this composite Update value.
                self.log(f"UPDATE entry with {silcb.update_json}")
                res = update_interface(silcb.update_json, silcb.local_keys["name"])
        elif silcb.cbtyp == AGT_CBTYP_T.AGT_CB_ROLLBACK:
            # undo device instrumentation here
            pass
        else:
            return self.flag_internal_error()

        return res

PY-SIL EDIT3 Update Example

The update_interface function modifies or creates an interface in the interfaces dictionary based on the provided key and data. It takes two parameters:

  • key: A string representing the key of the interface to update or create.

  • data: A dictionary containing the updated data for the interface.

If the interface associated with the given key already exists in the interfaces dictionary, its data is updated with the provided data. If the interface does not exist (i.e., the key is not found in the dictionary), a new entry is created in the interfaces dictionary with the provided data.

The function returns STATUS_T.NO_ERR to indicate a successful update or creation of the interface. Since the function always performs either an update or a creation, it always returns STATUS_T.NO_ERR. This function allows for the management of interface data by updating existing interfaces or creating new ones if they do not already exist.

# Example dictionary-like structure
interfaces = {
    "interface1": {"name": "interface1", "type": "ethernet", "enabled": 1},
    "interface2": {"name": "interface2", "type": "wifi", "enabled": 2},
    "interface3": {"name": "interface3", "type": "ethernet", "enabled": 1},
}


def update_interface(key, data):
    """
    Update interface data based on the given key.

    Parameters
    ----------
    key : str
        The key of the interface to update.
    data : dict
        A dictionary containing the updated data for the interface.

    Returns
    -------
    STATUS_T
        True if the update is successful.
    """
    if key in interfaces:
        interfaces[key].update(data)
    else:
        interfaces[key] = data

    return STATUS_T.NO_ERR

PY-SIL EDIT3 Delete Example

The delete_interface function removes an interface from the interfaces dictionary based on the provided key. It takes one parameter:

  • key: A string representing the key of the interface to delete. If the interface associated with the given key exists in the interfaces dictionary, it is removed, and the function returns STATUS_T.NO_ERR to indicate a successful deletion. If the interface does not exist (i.e., the key is not found in the dictionary), the function returns STATUS_T.ERR_NCX_NOT_FOUND.

The function provides the flexibility to manage the interface data by allowing the deletion of specific interfaces when they are no longer needed.

# Example dictionary-like structure
interfaces = {
    "interface1": {"name": "interface1", "type": "ethernet", "enabled": 1},
    "interface2": {"name": "interface2", "type": "wifi", "enabled": 2},
    "interface3": {"name": "interface3", "type": "ethernet", "enabled": 1},
}


def delete_interface(key):
    """
    Delete an interface based on the given key.

    Parameters
    ----------
    key : str
        The key of the interface to delete.

    Returns
    -------
    STATUS_T
        NO_ERR if deletion is successful, ERR_NCX_NOT_FOUND otherwise (e.g., key not found).
    """
    if key in interfaces:
        del interfaces[key]
        return STATUS_T.NO_ERR
    else:
        return STATUS_T.ERR_NCX_NOT_FOUND

PY-SIL RPC Callback

Any new operation can be added by defining a new YANG RPC statement in a module, and providing the proper PY-SIL code.

Common information about RPC callbacks related to the server is available in RPC Callbacks.

PY-SIL RPC Validate Callback

def validate_method_name(self, silcb: SILCbValidate)

The RPC validate callback method is optional to use. Its purpose is to validate any aspects of an RPC operation, beyond the constraints checked by the server engine. Only 1 validate method can register for each YANG rpc statement. The standard NETCONF operations are reserved by the server engine. There is usually zero or one of these callback method for every 'rpc' statement in the YANG module associated with the SIL code.

The PY-SIL Code Generation will generate validate methods with comments to simplify implementation.

Parameters:

silcb (SILCbValidate) -- RPC Validate control block contains all the input, output, and state data used for RPC transactions.

Returns:

STATUS_T.NO_ERR if executed OK

Return type:

STATUS_T

PY-SIL RPC Invoke Callback

def invoke_method_name(self, silcb: SILCbInvoke)

The RPC invoke callback method is used to perform the operation requested by the client session. Only 1 invoke method can register for each YANG rpc statement. The standard NETCONF operations are reserved by the server engine. There is usually one of these callback methods for every 'rpc' statement in the YANG module associated with the SIL code.

The RPC invoke callback method is optional to use, although if no invoke callback is provided, then the operation will have no affect. Normally, this is only the case if the module is be tested by an application developer, using netconfd-pro as a server simulator.

The PY-SIL Code Generation will generate validate methods with comments to simplify implementation.

Parameters:

silcb (SILCbInvoke) -- RPC Invoke control block contains all the input, output, and state data used for RPC transactions. It's basically the same as RPC Validate control block with added add_return_data() method to make it possible to add return data if any.

Returns:

STATUS_T.NO_ERR if executed OK

Return type:

STATUS_T

PY-SIL RPC Validate Callback Control Block

class pysil.module.SILCbValidate(scb: LP_STRUCT_SES_CB_T_, msg: LP_STRUCT_RPC_MSG_T_, methnode: LP_STRUCT_XML_NODE_T_, module_name: str, callback_info: dict, nsid: int)

Bases: SILCbValidateInvokeBase

The SILCbValidate class is a comprehensive control block used in RPC Validate callbacks. It encapsulates all necessary pointers and information required for the execution of the RPC Validate callback methods.

property callback_info: dict

Property to get callback_info

property client_addr: str

This field contains the client address involved in the transaction. or None

property errorval

Property errorval recorded by set_errorval method

property input_params: dict

Returns dict with all parameters for callback object

log(msg: str, log_level: str = 'INFO') None

Adds message to log

property methnode: LP_STRUCT_XML_NODE_T_

This pointer refers to the XML-specific node being parsed if available. This can be used for error reporting if no 'val' or 'obj' parameters are available (from the request message).

property module_name: str
property msg: LP_STRUCT_RPC_MSG_T_

This pointer refers to the incoming RPC message in progress, detailing the specific request being processed.

property nsid: int

namespace ID for silcb

property scb: LP_STRUCT_SES_CB_T_

This field contains a pointer to the session control block that initiated the request, representing the current session's context.

send_terminal_message(message: str) ENUM_STATUS_T_

Sends message to terminal

set_errorval(data)

Use method if you would like to set an errorval when error returned. The errorval will be recorded to the server.

property username: str

user_id value or None

property utils: PYSILUtils

returns utils for the silcb

This control block is pivotal in managing the intricacies of RPC Validate callbacks, ensuring that all necessary data and context are readily available for efficient processing.

PY-SIL RPC Invoke Callback Control Block

class pysil.module.SILCbInvoke(scb: LP_STRUCT_SES_CB_T_, msg: LP_STRUCT_RPC_MSG_T_, methnode: LP_STRUCT_XML_NODE_T_, module_name: str, callback_info: dict, nsid: int)

Bases: SILCbValidate

The SILCbInvoke class is a comprehensive control block used in RPC Invoke callbacks. It encapsulates all necessary pointers and information required for the execution of the RPC Invoke callback methods.

add_return_data(data: dict) ENUM_STATUS_T_

Adds data to return

Parameters:

data (dict) -- provide return data dict here

Returns:

Status

property callback_info: dict

Property to get callback_info

property client_addr: str

This field contains the client address involved in the transaction. or None

property errorval

Property errorval recorded by set_errorval method

property input_params: dict

Returns dict with all parameters for callback object

log(msg: str, log_level: str = 'INFO') None

Adds message to log

property methnode: LP_STRUCT_XML_NODE_T_

This pointer refers to the XML-specific node being parsed if available. This can be used for error reporting if no 'val' or 'obj' parameters are available (from the request message).

property module_name: str
property msg: LP_STRUCT_RPC_MSG_T_

This pointer refers to the incoming RPC message in progress, detailing the specific request being processed.

property nsid: int

namespace ID for silcb

property scb: LP_STRUCT_SES_CB_T_

This field contains a pointer to the session control block that initiated the request, representing the current session's context.

send_terminal_message(message: str) ENUM_STATUS_T_

Sends message to terminal

set_errorval(data)

Use method if you would like to set an errorval when error returned. The errorval will be recorded to the server.

property username: str

user_id value or None

property utils: PYSILUtils

returns utils for the silcb

This control block is pivotal in managing the intricacies of RPC Invoke callbacks, ensuring that all necessary data and context are readily available for efficient processing. It's basically the same as SILCbValidate with added method add_return_data().

PY-SIL RPC Callback Initialization and Cleanup

PY-SIL library provides functionality to register callback based on a configuration provided in a dictionary format. It register all callback specified in configuration during init phase 1 and unregister them during cleanup phase.

config = {
    # ...
    callbacks: [
        # ...
        {
            "path": "/toast:make-toast",
            "cb_validate": "validate_make_toast",
            "cb_invoke": "invoke_make_toast",
            "input_params": [
                {
                    "name": "toasterDoneness",
                    "type": "uint32",
                },
                {
                    "name": "toasterToastType",
                    "type": "identityref",
                },
            ],
        },
    ]
}
  • "path": "/toast:make-toast" This specifies the path where the callback "validate_make_toast" or "invoke_make_toast" should be called.

  • "cb_validate": "validate_make_toast" This specifies the name of the validate callback method that should be called when a request is made to the specified path. In this case, the callback named "validate_make_toast" will be called.

  • "cb_invoke": "invoke_make_toast" This specifies the name of the invoke callback method that should be called when a request is made to the specified path. In this case, the callback named "invoke_make_toast" will be called.

  • "input_params": [] This specifies list of input parameters for the callback. Each parameter has:

    • name - yang name for a node.

    • type - type of the parameter.

PY-SIL RPC Callback Examples

Example RPC YANG definition:

    rpc make-toast {
        description
          "Make some toast.
           The toastDone notification will be sent when
           the toast is finished.
           An 'in-use' error will be returned if toast
           is already being made.
           A 'resource-denied' error will be returned
           if the toaster service is disabled.";
        input {
            leaf toasterDoneness {
                type uint32 {
                    range "1 .. 10";
                }
                default 5;
                description
                  "This variable controls how well-done is the
                   ensuing toast. It should be on a scale of 1 to 10.
                   Toast made at 10 generally is considered unfit
                   for human consumption; toast made at 1 is warmed
                   lightly.";
            }
            leaf toasterToastType {
                type identityref {
                    base toast:toast-type;
                }
                default toast:wheat-bread;
                description
                  "This variable informs the toaster of the type of
                   material that is being toasted. The toaster
                   uses this information, combined with
                   toasterDoneness, to compute for how
                   long the material must be toasted to achieve
                   the required doneness.";
            }
        }
    }

Example PY-SIL RPC Validate Method:

class PYSILModuleToaster(PYSILModuleToasterBase):
    # ...
    def validate_make_toast(self, silcb: SILCbValidate):
        """
        Validation phase callback for "<validate_make_toast>" operation.
        All YANG constraints have passed at this point.
        Add description-stmt checks in this function.
        Path: rpc /toast:make-toast
        Description:
            Make some toast.
            The toastDone notification will be sent when
            the toast is finished.
            An 'in-use' error will be returned if toast
            is already being made.
            A 'resource-denied' error will be returned
            if the toaster service is disabled.

        Parameters
        ----------
            silcb: SILCbValidate
                Control block

        Returns
        -------
            STATUS_T
                Return status for the phase.
                An error in validate phase will cancel invoke phase
                An rpc-error will be added if an error is returned and the msg error Q is empty

        Additional
        ----------
            silcb.input_params: dict with input parameters
                silcb.input_params["toasterDoneness"]: int
                    This variable controls how well-done is the
                    ensuing toast. It should be on a scale of 1 to 10.
                    Toast made at 10 generally is considered unfit
                    for human consumption; toast made at 1 is warmed
                    lightly.
                silcb.input_params["toasterToastType"]: identityref
                    This variable informs the toaster of the type of
                    material that is being toasted. The toaster
                    uses this information, combined with
                    toasterDoneness, to compute for how
                    long the material must be toasted to achieve
                    the required doneness.
        """

        self.log(
            f"Enter 'validate_make_toast' callback toaster@2009-11-20 input_params={silcb.input_params}"
        )

        res = STATUS_T.NO_ERR

        if self.toaster_state.enabled:
            # toaster service enabled, check if in use

            if self.toaster_state.toasting:
                res = STATUS_T.ERR_NCX_IN_USE
            else:
                # this is where a check on bread inventory would go

                # this is where a check on toaster HW ready would go
                pass
        else:
            # toaster service disabled
            res = STATUS_T.ERR_NCX_RESOURCE_DENIED

        # if you have an error you also could set errorval
        # silcb.set_errorval("testerrorval")

        return res

Example PY-SIL RPC Invoke Method:

class PYSILModuleToaster(PYSILModuleToasterBase):
    # ...
    def invoke_make_toast(self, silcb: SILCbInvoke):
        """
        Invocation phase callback for "<invoke_make_toast>" operation.
        Validation callback has passed at this point.
        Call device instrumentation code in this function.
        Path: rpc /toast:make-toast
        Description:
            Make some toast.
            The toastDone notification will be sent when
            the toast is finished.
            An 'in-use' error will be returned if toast
            is already being made.
            A 'resource-denied' error will be returned
            if the toaster service is disabled.

        Parameters
        ----------
            silcb: SILCbInvoke
                Control block

        Returns
        -------
            STATUS_T
                Return status for the phase.
                An error in validate phase will cancel invoke phase
                An rpc-error will be added if an error is returned and the msg error Q is empty

        Additional
        ----------
            silcb.input_params: dict with input parameters
                silcb.input_params["toasterDoneness"]: int
                    This variable controls how well-done is the
                    ensuing toast. It should be on a scale of 1 to 10.
                    Toast made at 10 generally is considered unfit
                    for human consumption; toast made at 1 is warmed
                    lightly.
                silcb.input_params["toasterToastType"]: identityref
                    This variable informs the toaster of the type of
                    material that is being toasted. The toaster
                    uses this information, combined with
                    toasterDoneness, to compute for how
                    long the material must be toasted to achieve
                    the required doneness.
        """

        self.log(
            f"Enter 'invoke_make_toast' callback toaster@2009-11-20 input_params={silcb.input_params}"
        )

        res = STATUS_T.NO_ERR

        toaster_doneness = silcb.input_params["toasterDoneness"]
        # make sure the toaster_doneness value is set
        if toaster_doneness is None:
            toaster_doneness = 5  # set the default

        # arbitrary formula to convert toaster doneness to the
        # number of seconds the toaster should be on
        # In the example each doneness is 12 seconds
        toaster_duration = toaster_doneness * 12

        # this is where the code would go to adjust the duration
        # based on the bread type
        self.log(f"toaster: starting toaster for {toaster_duration} seconds")

        # this is where the code would go to start the toaster
        # heater element

        # start a timer to toast for the specified time interval
        res, timer_id = self.timer_create(
            self.toaster_timer_fn, seconds=toaster_duration
        )

        if res == STATUS_T.NO_ERR:
            self.toaster_state.toasting = True
            self.toaster_state.timer_id = timer_id
        return res

PY-SIL RPC Data Output Handling Example

Example YANG Module:

module addrpc {
  namespace "http://www.yumaworks.com/ns/addrpc";
  prefix add;
  revision "2020-02-25";

  rpc add {
    description "Get the sum of two numbers";
    input {
      leaf num1 {
        type int32;
        mandatory true;
        description "First number to add";
      }
      leaf num2 {
        type int32;
        mandatory true;
        description "Second number to add";
      }
    }
    output {
      leaf sum {
        type int32;
        mandatory true;
        description "The sum of the 2 numbers";
      }
    }
   }
}

Example PY-SIL RPC Invoke Method Returning Output Data:

class PYSILModuleAddrpc(PYSILModuleAddrpcBase):
    # ...
    def invoke_add(self, silcb: SILCbInvoke):
        """
        Invocation phase callback for "<invoke_add>" operation.
        Validation callback has passed at this point.
        Call device instrumentation code in this function.
        Path: rpc /add:add
        Description: Get the sum of two numbers

        Parameters
        ----------
            silcb:SILCbInvoke
                Control block

        Returns
        -------
            STATUS_T
                Return status for the phase.
                An error in validate phase will cancel invoke phase
                An rpc-error will be added if an error is returned and the msg error Q is empty

        Additional
        ----------
            silcb.input_params: dict with input parameters
                silcb.input_params["num1"]: int - First number to add
                silcb.input_params["num2"]: int - Second number to add
        """

        self.log(
            f"Enter 'invoke_add' callback addrpc@2020-02-25 input_params={silcb.input_params}"
        )

        res = STATUS_T.NO_ERR

        # Calculate sum of num1 and num2 and return
        result = silcb.input_params["num1"] + silcb.input_params["num2"]
        return_data = {
            "sum": result,  # *required - replace value with correct <int>
        }

        # Save return data
        res = silcb.add_return_data(data=return_data)

        return res

PY-SIL Action Callback

A YANG Action is like an RPC operation in many ways except it is associated with the data node where the Action is defined. The PY-SIL code for an Action callback is a combination of the RPC callbacks and the EDIT callbacks. The call flow is the same as for RPC operations. The parameters are similar as well, except the ancestor keys for the instance are provided, so the PY-SIL callback knows which instance to apply the Action.

All YANG Actions are data-driven within the server, using the YANG Action statement for the operation and PY-SIL callbacks.

Any new data-specific Actions can be added by defining a new YANG Action statement within a container or list, and providing the proper PY-SIL code.

Common information about Action Callbacks related to the server is available in Action Callbacks.

PY-SIL Action Validate Callback

def action_validate_method_name(self, silcb: SILCbActionValidate)

The action validate callback method is optional to use. Its purpose is to validate any aspects of an Action request, beyond the constraints checked by the server engine.

  • The validate callback is optional to use.

  • Validation could be done in the validate or invoke phase with the same result.

The PY-SIL Code Generation will generate Action validate methods with comments to simplify implementation.

Parameters:

silcb (SILCbActionValidate) -- Action Validate control block contains all the input, output, and state data used for the transaction.

Returns:

STATUS_T.NO_ERR if executed OK

Return type:

STATUS_T

PY-SIL Action Invoke Callback

def action_invoke_method_name(self, silcb: SILCbInvoke)

The action invoke callback method is used to perform the data-specific action requested by the client session.

  • The action invoke callback method is optional to use, although if no invoke callback is provided, then the action will have no affect.

The PY-SIL Code Generation will generate validate methods with comments to simplify implementation.

param silcb:

Action Invoke control block contains all the input, output, and state data used for the transaction. It's basically the same as Action Validate control block with added add_return_data() callback to make it possible to add return data if any.

type silcb:

SILCbActionInvoke

return:

STATUS_T.NO_ERR if executed OK

rtype:

STATUS_T

PY-SIL Action Validate Callback Control Block

class pysil.module.SILCbActionValidate(scb: LP_STRUCT_SES_CB_T_, msg: LP_STRUCT_RPC_MSG_T_, methnode: LP_STRUCT_XML_NODE_T_, actionval: LP_STRUCT_VAL_VALUE_T_, module_name: str, callback_info: dict, nsid: int)

Bases: SILCbValidateInvokeBase

The SILCbActionValidate class is a comprehensive control block used in Action Validate callbacks. It encapsulates all necessary pointers and information required for the execution of the Action Validate callback methods.

property actionval: LP_STRUCT_VAL_VALUE_T_

Pointer to the input node represents the start of the specific action being invoked. The auto-generated SIL code can derive all the ancestor keys from this data structure.

property ancestor_keys: dict

Ancestors keys for silcb

property callback_info: dict

Property to get callback_info

property client_addr: str

This field contains the client address involved in the transaction. or None

property errorval

Property errorval recorded by set_errorval method

property input_params: dict

Returns dict with all parameters for callback object

log(msg: str, log_level: str = 'INFO') None

Adds message to log

property methnode: LP_STRUCT_XML_NODE_T_

This pointer refers to the XML-specific node being parsed if available. This can be used for error reporting if no 'val' or 'obj' parameters are available (from the request message).

property module_name: str
property msg: LP_STRUCT_RPC_MSG_T_

This pointer refers to the incoming RPC message in progress, detailing the specific request being processed.

property nsid: int

namespace ID for silcb

property scb: LP_STRUCT_SES_CB_T_

This field contains a pointer to the session control block that initiated the request, representing the current session's context.

send_terminal_message(message: str) ENUM_STATUS_T_

Sends message to terminal

set_errorval(data)

Use method if you would like to set an errorval when error returned. The errorval will be recorded to the server.

property username: str

user_id value or None

property utils: PYSILUtils

returns utils for the silcb

This control block is pivotal in managing the intricacies of Action Validate callbacks, ensuring that all necessary data and context are readily available for efficient processing.

PY-SIL Action Invoke Callback Control Block

class pysil.module.SILCbActionInvoke(scb: LP_STRUCT_SES_CB_T_, msg: LP_STRUCT_RPC_MSG_T_, methnode: LP_STRUCT_XML_NODE_T_, actionval: LP_STRUCT_VAL_VALUE_T_, module_name: str, callback_info: dict, nsid: int)

Bases: SILCbActionValidate

The SILCbActionInvoke class is a comprehensive control block used in Action Invoke callbacks. It encapsulates all necessary pointers and information required for the execution of the Action Invoke callback methods.

property actionval: LP_STRUCT_VAL_VALUE_T_

Pointer to the input node represents the start of the specific action being invoked. The auto-generated SIL code can derive all the ancestor keys from this data structure.

add_return_data(data)

Adds data to return

Parameters:

data (dict) -- provide return data dict here

Returns:

Status

property ancestor_keys: dict

Ancestors keys for silcb

property callback_info: dict

Property to get callback_info

property client_addr: str

This field contains the client address involved in the transaction. or None

property errorval

Property errorval recorded by set_errorval method

property input_params: dict

Returns dict with all parameters for callback object

log(msg: str, log_level: str = 'INFO') None

Adds message to log

property methnode: LP_STRUCT_XML_NODE_T_

This pointer refers to the XML-specific node being parsed if available. This can be used for error reporting if no 'val' or 'obj' parameters are available (from the request message).

property module_name: str
property msg: LP_STRUCT_RPC_MSG_T_

This pointer refers to the incoming RPC message in progress, detailing the specific request being processed.

property nsid: int

namespace ID for silcb

property scb: LP_STRUCT_SES_CB_T_

This field contains a pointer to the session control block that initiated the request, representing the current session's context.

send_terminal_message(message: str) ENUM_STATUS_T_

Sends message to terminal

set_errorval(data)

Use method if you would like to set an errorval when error returned. The errorval will be recorded to the server.

property username: str

user_id value or None

property utils: PYSILUtils

returns utils for the silcb

This control block is pivotal in managing the intricacies of Action Invoke callbacks, ensuring that all necessary data and context are readily available for efficient processing. It's basically the same as SILCbActionValidate with added method add_return_data().

PY-SIL Action Callback Initialization and Cleanup

PY-SIL library provides functionality to register callbacks based on a configuration provided in a dictionary format. It register all callback specified in configuration during Init phase 1 and unregister them during cleanup phase.

config = {
    # ...
    callbacks: [
        # ...
        {
            "path": "/exa:server/reset",
            "cb_action_validate": "action_validate_reset",
            "cb_action_invoke": "action_invoke_reset",
            "lvl": 3,
            "keys": [
                {
                    "name": "name",
                    "pname": "server__name",
                    "type": "string",
                    "lvl": 2,
                }
            ],
            "input_params": [
                {
                    "name": "reset-msg",
                    "type": "string",
                }
            ],
        },
    ]
}
  • "path": "/exa:server/reset" This specifies the path where the callback "action_validate_reset" or "action_invoke_reset" should be called.

  • "cb_action_validate": "action_validate_reset" This specifies the name of the validate callback method that should be called when a request is made to the specified path. In this case, the callback named "action_validate_reset" will be called.

  • "cb_action_invoke": "action_invoke_reset" This specifies the name of the invoke callback method that should be called when a request is made to the specified path. In this case, the callback named "action_invoke_reset" will be called.

  • "keys": [] This specifies list of keys for the callback. Each key has:

    • name - yang name for a node.

    • pname - python parameter name in keys list.

    • type - type of the key.

    • lvl - level in the node's tree for the key node.

  • "input_params": [] This specifies list of input parameters for the callback. Each parameter has:

    • name - yang name for a node.

    • type - type of the parameter.

PY-SIL Action Callback Examples

Example Action YANG definition:

module ex-action {
  yang-version 1.1;
  namespace "http://netconfcentral.org/ns/ex-action";
  prefix exa;
  import ietf-yang-types { prefix yang; }
  revision 2020-03-06;

  list server {
    key name;
    leaf name {
      type string;
      description "Server name";
    }
    action reset {
      input {
        leaf reset-msg {
          type string;
          description "Log message to print before server reset";
        }
      }
      output {
        leaf reset-finished-at {
          type yang:date-and-time;
          description "Time the reset was done on the server";
        }
      }
    }
  }
}

Example PY-SIL Action Validate Method:

class PYSILModuleExAction(PYSILModuleExActionBase):
    # some methods here

    def action_validate_reset(self, silcb: SILCbActionValidate):
        """
        YANG 1.1 action validate callback.
        Path: action /exa:server/reset

        Parameters
        ----------
            silcb: SILCbActionValidate
                Control block

        Returns
        -------
            STATUS_T
                Return status for the phase.
                An error in validate phase will cancel invoke phase
                An rpc-error will be added if an error is returned and the msg error Q is empty

        Additional
        ----------
            silcb.ancestor_keys: dict with ancestor keys
                silcb.ancestor_keys["server__name"]: str
                    Ancestor key leaf 'name' in list 'server' Path: /exa:server/name
            silcb.input_params: dict with input parameters
                silcb.input_params["reset-msg"]: str
                    Log message to print before server reset
        """

        self.log(
            f"Enter 'action_validate_reset' callback ex-action@2020-03-06 ancestor_keys={silcb.ancestor_keys} input_params={silcb.input_params}"
        )

        res = STATUS_T.NO_ERR

        """
        Ancestor keys are available in silcb.ancestor_keys dict
        silcb.ancestor_keys["server__name"]: str # Path: /exa:server/name
        """

        # the validate function would check here if it is OK to
        # reset the server right now; if not an error would be
        # returned by setting res to the correct error status.
        # If the input parameter is the problem then set errorval
        # silcb.set_errorval("testerrorval")
        # otherwise leave errorval NULL

        return res

Example PY-SIL Action Invoke Method:

class PYSILModuleExAction(PYSILModuleExActionBase):
    # some methods here

    def action_invoke_reset(self, silcb: SILCbActionInvoke):
        """
        YANG 1.1 action invoke callback.
        Path: action /exa:server/reset

        Parameters
        ----------
            silcb: SILCbActionInvoke
                Control block

        Returns
        -------
            STATUS_T
                Return status for the phase.
                An error in validate phase will cancel invoke phase
                An rpc-error will be added if an error is returned and the msg error Q is empty

        Additional
        ----------
            silcb.ancestor_keys: dict with ancestor keys
                silcb.ancestor_keys["server__name"]: str
                    Ancestor key leaf 'name' in list 'server' Path: /exa:server/name
            silcb.input_params: dict with input parameters
                silcb.input_params["reset-msg"]: str
                    Log message to print before server reset
        """

        self.log(
            f"Enter 'action_invoke_reset' callback ex-action@2020-03-06 ancestor_keys={silcb.ancestor_keys} input_params={silcb.input_params}"
        )

        # display the reset logging message
        self.log(
            f'ex-action: Resetting server {silcb.ancestor_keys["server__name"]}. MSG: {silcb.input_params["reset-msg"]}'
        )

        res = STATUS_T.NO_ERR

        # the invoke function would schedule or execute the server reset here

        """
        Ancestor keys are available in silcb.ancestor_keys dict
        silcb.ancestor_keys["server__name"]: str # Path: /exa:server/name
        """

        # Item found. Create a python object (dict, list, etc) with item data
        from datetime import datetime, timezone

        return_data = {
            "reset-finished-at": datetime.now(
                timezone.utc
            ).isoformat(),  # remove or replace value with correct <yang:date-and-time>
        }

        # Save return data
        res = silcb.add_return_data(data=return_data)

        return res

PY-SIL Action Data Output Handling

YANG Actions can return data to the client if the operation succeeds. The YANG “output” statement defines the return data for each YANG Action. Constructing YANG data is covered in detail elsewhere. The previous example from PY-SIL Action Invoke Callback shows a simple example PY-SIL Action invoke callback method that returns data.

This procedure is the same for PY-SIL RPC output handling. Refer to the PY-SIL RPC Data Output Handling Example section and follow these procedures.

PY-SIL Notifications

The PY-SIL Code Generation will automatically generate methods to queue a specific notification type for processing. It is up to the PY-SIL callback code to invoke this callback when the notification event needs to be generated. The PY-SIL code is expected to provide the values that are needed for any notification data dictionary.

The send_notification(...) method is not a callback. Its purpose is to generate a specific event, but the same method could be used for multiple event types. If the notification-stmt for the event type defines child nodes then the send method is expected to receive this data (in dict) when the method is called.

PY-SIL can include an Event Stream Callback function to know if any clients are listening on the relevant event stream. If no subscriptions are active, then the notification send processing logic can decide to suppress generation of certain events.

PY-SIL Send Notification

This method is not called by the server directly. It can be changed or not used at all to generate a notification event.

def send_notification(self, name:str, data:dict=None, stream_name:str=None) -> None

Send a notification-event to the main server.

Parameters:
  • name (str) -- Notification name.

  • data (dict) -- Dictionary with notification nodes data if any.

  • stream_name (str) -- A stream name to send notification to. "None" means default (netconf).

Returns:

None

Return type:

None

Send notification for PY-SIL

Send to the default or configured event stream for PY-SIL (PYSILModuleBase).

class PYSILModuleToaster(PYSILModuleToasterBase):
  #...
  def send_toastDone(self, data: dict):
    #...
    self.send_notification(name="toastDone", data=data)

Send notification for PY-SIL to a Specific Event Stream

class PYSILModuleToaster(PYSILModuleToasterBase):
  #...
  def send_toastDone(self, data: dict):
    #...
    self.send_notification(name="toastDone", data=data, stream_name="stream1")

PY-SIL Notification Send Example

In the example below, the 'toastDone' notification event contains just one leaf, called the 'toastStatus'. There is PY-SIL timer callback code which calls this method, and provides the final toast status, after the <make-toast> operation has been completed or canceled.

class PYSILModuleToaster(PYSILModuleToasterBase):
    # ...
    def invoke_make_toast(self, silcb: SILCbInvoke):
        """Method to start make a toast (start timer)"""
        # ...
        res = STATUS_T.NO_ERR
        # ...
        # start a timer to toast for the specified time interval (20 seconds here)
        # call self.toaster_timer_fn(...) in 20 seconds
        res, timer_id = self.timer_create(self.toaster_timer_fn, seconds=20)

        if res == STATUS_T.NO_ERR:
            self.toaster_state.toasting = True
            self.toaster_state.timer_id = timer_id
        return res

    def toaster_timer_fn(self, timer_id: int, cookie: dict):
        """Custom added method to process timer events"""
        if self.toaster_state.timer_id != timer_id:
            self.log("Got wrong timer_id for toaster")
            return 0

        # toast is finished
        self.toaster_state.toasting = False
        self.toaster_state.timer_id = 0
        self.log(f"toaster[{self.toaster_state.id}] is finished")

        # send notification with toastStatus is "done"
        self.send_toastDone({"toastStatus": "done"})
        return 0

    def send_toastDone(self, data: dict):
        """
        Send a "<toastDone>" notification.
        Path: /toast:toastDone

        Called by your code when notification event occurs.

        This API is optional. It can be replaced with
        any function of your choice that provides the same functionality.

        Create an internal notification message (agt_not_msg_t) and queue
        it for delivery to client sessions.

        The parameters depend in the data definitions within the
        notification-stmt for this event type.

        Parameters
        ----------
            data: dict
                Notification data in format:
                data = {
                    "toastStatus": None,  # remove or replace value with correct <int>
                }
                example: self.send_toastDone({"toastStatus": "done"})

        Returns
        -------
            None
        """
        self.log(f"Enter 'send_toastDone' notification toaster@2009-11-20 data={data}")

        self.send_notification(name="toastDone", data=data)