PY-SIL py-sil-app
py-sil-app
The py-sil-app program provides a complete functioning application for implementing YANG object callback functions in a subsystem. It provides complete functionality for the PY-SIL Subsystem. The netconfd-pro program must be built with the make flags WITH_PY_SIL and WITH_YCONTROL.
Note
This program is a complete application. The PY-SIL functionality will only be supported if this subsystem application is used correctly.
SYNOPSIS
py-sil-app [--pymodpath=string] [--address=string] [--port=num]
[--subsys=<subsys-id>] [--library=name] [--retry-limit=num] [--library=name]
[<logging-parameters>]
USAGE
The py-sil-app program does not use a YANG-based configuration file.
If py-sil-app is called without any parameters, it will attempt to connect to the server with default settings (e.g. subsys-id=subsys1).
py-sil-app supports the following CLI parameters:
--address=string
The --address parameter is optional. It represents the IP address of the main server that will be used for the session. The default is localhost.
--library=string
The --library parameter keyword is optional. It specifies a PY-SIL library (module or bundle) that should be selected by this subsystem. If any --library parameters are present, then only those PY-SIL libraries will be loaded by this subsystem, if the main server indicates that the module or bundle is loaded. This option can be used multiple times, once for each selected module or bundle.
If no --library parameters are present then the py-sil-app will attempt to load all PY-SIL modules and bundles reported by the server.
If the PY-SIL library is not found then it will be skipped by the subsystem.
Example: --library=foo (selects libfoo_sa.so)
--log-level=enum
The --log-level, --log, and other logging parameters are also supported.
--port=uint16
The --port parameter is optional. It represents the TCP port number of the main server that will be used for the session. The default is the local system, determined by the local netconf-subsystem-pro.
--pymodpath=string Specifies the PY-SIL libraries search path to use while searching for PY-SIL libraries.
--retry-limit=num
The --retry-limit parameter will cause the subsystem to terminate after the specified number of re-connect attempts. The default value is 5000 re-connect attempts. The parameter is a uint16 number between 1 and 65535.
--subsys-id=string
The --subsys-id parameter keyword is optional. It specifies the subsystem identifier to use when registering with the netconfd-pro server. The default is 'subsys1' if no value is specified.
Starting PY-SIL Subsystems with py-sil-app
If the server is built using the WITH_PY_SIL=1 and WITH_YCONTROL=1 or EVERYTHING=1 make flag then it will listen for "sil-sa" service connections from PY-SIL subsystems.
The py-sil-app program can be used to support the PY-SIL libraries on a subsystem.
A subsystem can run on the same system as netconfd-pro or a different system.
A subsystem can be started before or after the main server. Connect and re-connect functionality is built into the program.
The PY-SIL libraries loaded depends on 2 factors:
The <module> and <bundle> parameters included in the <register-response> message from the server. The py-sil-app program will attempt to find the PY-SIL libraries for these modules and bundles.
By default the program will look in the
./
and~/pysil
directories. If a library is not found, then the module will be skipped. The PY-SIL libraries supported on a subsystem can be controlled by limiting which PY-SIL libraries are present.Optional you can provide --pymodpath to specify search path to use while searching for PY-SIL libraries.
Example: Start the server with a non-default socket:
# server started
> netconfd-pro --socket-type=tcp --socket-address=192.168.0.45 --socket-port=8090
# py-sil-app started
> py-sil-app --subsys-id=sub1 --address=192.168.0.45 --port=8090
Example 2: Start 2 subsystems on the same host:
# server started
> netconfd-pro --module=mod1 --module=mod2
# py-sil-app started
> py-sil-app --subsys-id=sub1 --library=mod1
> py-sil-app --subsys-id=sub2 --library=mod2
PY-SIL py-sil-app CLI Reference
PY-SIL Application Example
The following file can be found in
the /usr/share/yumapro/py-sil-app/py-sil-app.py
directory.
#!/usr/bin/env python3
"""
----------------------------------------------------------------------------
Copyright (c) 2024, YumaWorks, Inc., All Rights Reserved.
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
----------------------------------------------------------------------------
"""
import os
import sys
import time
import pycontrol
import pysil
from typing import *
from pysilcommon import types as pysiltypes
from pysilcommon.addons import set_py_sil_modpath, set_log_level, enable_log_colors
from pysilcommon.addons import log as addon_log
STATUS_T = pysiltypes.STATUS_T_
def log(msg: str, log_level="INFO"):
"""Logging function"""
addon_log(f"PY-SIL-APP: {msg}", log_level)
def get_argv_param(
argv: list,
name: str,
err_empty: bool = False,
error_not_present: bool = False,
remove: bool = False,
) -> (int, str):
"""
search parameters in argv list
argv - list of cli parameters
name - name of parameter to search
err_empty - returns error if parameter exists but empty
error_not_present - returns error if parameter doesn't exist
remove - remove from argv
* RETURNS:
* (status code, value)
"""
full_name = f"--{name}"
l = len(full_name)
val = None
for i, v in enumerate(argv):
if v.startswith(full_name):
if l == len(v):
# found, no value means bool True
val = True
elif v[l] == "=":
# found, value after "="
val = v[(l + 1) :]
else:
# diffenet parameter with the same start letters
continue
if remove:
# remove from argv
del argv[i]
break
if (error_not_present and val is None) or (err_empty and val == ""):
return (STATUS_T.ERR_NCX_INVALID_VALUE, val)
return (STATUS_T.NO_ERR, val)
def print_usage():
"""
* FUNCTION print_usage
*
* Print the program usage text
"""
print("\nUsage:")
print(
"\n py-sil-app [--pymodpath=string] [<logging-parameters>] "
"[--subsys-id=string] [--library=name] [--retry-limit=num]"
)
print(
"\nPy-sil-app is a sample YumaPro subsystem program to demonstrate how "
"the PY-SIL and YControl libraries can be used within an application."
"\nRefer to the online YumaPro Developer Manual for full details on PY-SIL development:"
"\n\nhttps://docs.yumaworks.com/en/latest/dev/index.html."
)
print("\nExample:")
print("\n py-sil-app --pymodpath=/home/user/pysil --library=sample")
exit()
def get_cli_parms(argv: list) -> (pysiltypes.status_t, dict):
"""
/********************************************************************
* FUNCTION get_cli_parms
*
* Check the CLI parameters for the getconfig commands
*
* RETURNS:
* status code
* dict with parameters:
char **address,
uint16 *port,
boolean *if_notif,
uint16 *retry_limit
*********************************************************************/
"""
params = {
"address": None,
"port": 0,
"if_notif": False,
"retry_limit": 0,
"log_level": 3, # info
}
ncx_invalid_value_response = (STATUS_T.ERR_NCX_INVALID_VALUE, params)
ad = "--address="
po = "--port="
li = "--library="
if_notif = "--with-notif"
rl = "--retry-limit="
i = 1
for a in argv:
if a.startswith(ad):
params["address"] = a[len(ad) :]
if not params["address"]:
return ncx_invalid_value_response
elif a.startswith(if_notif):
params["if_notif"] = True
elif a.startswith(po):
try:
params["port"] = int(a[len(po) :])
if not (0 < params["port"] <= 65535):
return ncx_invalid_value_response
except:
return ncx_invalid_value_response
elif a.startswith(li):
library = a[len(li) :]
if not library:
return ncx_invalid_value_response
res = pysil.sil_sa_add_library_parm(library)
if res != STATUS_T.NO_ERR:
return ncx_invalid_value_response
if "library" not in params:
params["library"] = []
params["library"].append(library)
elif a.startswith(rl):
try:
params["retry_limit"] = int(a[len(rl) :])
if not (0 < params["retry_limit"] <= 65535):
return ncx_invalid_value_response
except:
return ncx_invalid_value_response
# else ignore because probably an NCX CLI parm
return (STATUS_T.NO_ERR, params)
# end get_cli_parms
if __name__ == "__main__":
argv = sys.argv
# get log_level param
res, log_level = get_argv_param(argv, name="log-level")
# set log level value
if log_level:
set_log_level(log_level)
# Redirect stdout to a file if --log= param provided to app
res, log_file_name = get_argv_param(argv, name="log", remove=True)
res, log_append = get_argv_param(argv, name="log-append", remove=True)
if log_file_name:
mode = "a" if log_append else "w"
log_file = open(log_file_name, mode)
sys.stdout.flush()
os.dup2(log_file.fileno(), sys.stdout.fileno())
else:
# allow colors for console only
enable_log_colors()
try:
# log(f"START PY-SIL")
ycontrol_done = False
# set PY_SIL_MODPATH from input parameters
res, pymodpath = get_argv_param(
argv, name="pymodpath", err_empty=True, error_not_present=False, remove=True
)
if res != STATUS_T.NO_ERR:
print_usage()
if pymodpath:
set_py_sil_modpath(pymodpath)
"""
need to check for the subsys-id parm before
the system is initialized
"""
res, subsys = get_argv_param(argv, name="subsys-id", err_empty=True)
if res != STATUS_T.NO_ERR:
print_usage()
# 1) setup yumapro messaging service profile
if res == STATUS_T.NO_ERR:
if subsys is None:
subsys = "subsys1"
log(f"subsys={subsys}")
# cut parameters - remove name of python file
# argv = sys.argv[1:]
log(f"Pass parameters to ycontrol_init: {argv}")
log("pycontrol.ycontrol_init")
pycontrol.ycontrol_init(len(argv), argv, subsys)
log(f"ycontrol_init res={res}")
ycontrol_done = True
# 2) register services with the control layer
if res == STATUS_T.NO_ERR:
log("pysil.sil_sa_register_service")
res = pysil.sil_sa_register_service()
log(f"sil_sa_register_service res={res}")
# get the CLI parameters after the system is initialized
# so library parameter handled correctly
if res == STATUS_T.NO_ERR:
res, params = get_cli_parms(argv)
if res != STATUS_T.NO_ERR:
print_usage()
# set the retry limit if provided
if res == STATUS_T.NO_ERR and params["retry_limit"] > 0:
log("pycontrol.ycontrol_set_retry_limit")
pycontrol.ycontrol_set_retry_limit(params["retry_limit"])
# 3) do 2nd stage init of the control manager (connect to server)
if res == STATUS_T.NO_ERR:
if params["address"]:
if params["port"] == 0:
params["port"] = 2023
# res = ycontrol_init2_ha("server1", address, port)
res = pycontrol.ycontrol_init2_ha(
"server1", params["address"], params["port"]
)
else:
log("pycontrol.ycontrol_init2")
res = pycontrol.ycontrol_init2()
sleep_val = 0.01 # 1/100 sec
done = False
# 4) call ycontrol_check_io periodically from the main program control loop
log("wait cycle")
while not done and res == STATUS_T.NO_ERR:
res = pycontrol.ycontrol_check_io()
if pycontrol.ycontrol_shutdown_now():
# YControl has received a <shutdown-event>
# from the server subsystem is no longer active
# could ignore or shut down YControl IO loop
log(f"YControl has received a <shutdown-event>")
done = True
# Using sleep to represent other program work; remove for real
if not done and res == STATUS_T.NO_ERR:
time.sleep(sleep_val)
log("Done, result: %s" % res)
except Exception as e:
print(f"An error occurred: {e}")
# Close the log file when done
if log_file_name:
log_file.close()