mirror of
https://dev.iopsys.eu/bbf/bbfdm.git
synced 2025-12-10 07:44:39 +01:00
476 lines
14 KiB
Python
Executable file
476 lines
14 KiB
Python
Executable file
#!/usr/bin/python3
|
|
|
|
# Copyright (C) 2021 iopsys Software Solutions AB
|
|
# Author: Amin Ben Ramdhane <amin.benramdhane@pivasoftware.com>
|
|
|
|
import sys
|
|
import os
|
|
import subprocess
|
|
import shutil
|
|
import json
|
|
import ubus
|
|
import time
|
|
import glob
|
|
|
|
# Constants
|
|
BBF_ERROR_CODE = 0
|
|
CURRENT_PATH = os.getcwd()
|
|
BBF_DMTREE_PATH = os.path.join(CURRENT_PATH, "libbbfdm", "dmtree")
|
|
BBF_DMTREE_PATH_TR181_JSON = os.path.join(BBF_DMTREE_PATH, "json", "tr181.json")
|
|
BBF_DMTREE_PATH_TR104_JSON = os.path.join(BBF_DMTREE_PATH, "json", "tr104.json")
|
|
ARRAY_JSON_FILES = {"tr181": BBF_DMTREE_PATH_TR181_JSON, "tr104": BBF_DMTREE_PATH_TR104_JSON}
|
|
|
|
LIST_SUPPORTED_USP_DM = []
|
|
LIST_SUPPORTED_CWMP_DM = []
|
|
|
|
Array_Types = {
|
|
"string": "DMT_STRING",
|
|
"unsignedInt": "DMT_UNINT",
|
|
"unsignedLong": "DMT_UNLONG",
|
|
"int": "DMT_INT",
|
|
"long": "DMT_LONG",
|
|
"boolean": "DMT_BOOL",
|
|
"dateTime": "DMT_TIME",
|
|
"hexBinary": "DMT_HEXBIN",
|
|
"base64": "DMT_BASE64",
|
|
"command": "DMT_COMMAND",
|
|
"event": "DMT_EVENT"
|
|
}
|
|
|
|
|
|
def rename_file(old_path, new_path):
|
|
try:
|
|
os.rename(old_path, new_path)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def move_file(source_path, destination_path):
|
|
shutil.move(source_path, destination_path)
|
|
|
|
|
|
def remove_file(file_path):
|
|
try:
|
|
os.remove(file_path)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def create_folder(folder_path):
|
|
try:
|
|
os.makedirs(folder_path, exist_ok=True)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def rmtree_handler(_func, path, _exc_info):
|
|
print(f'Failed to remove {path}')
|
|
|
|
|
|
def remove_folder(folder_path):
|
|
if os.path.isdir(folder_path):
|
|
shutil.rmtree(folder_path, onerror=rmtree_handler)
|
|
|
|
|
|
def cd_dir(path):
|
|
try:
|
|
os.chdir(path)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def obj_has_child(value):
|
|
if isinstance(value, dict):
|
|
for _obj, val in value.items():
|
|
if isinstance(val, dict):
|
|
for obj1, val1 in val.items():
|
|
if obj1 == "type" and val1 == "object":
|
|
return True
|
|
return False
|
|
|
|
|
|
def obj_has_param(value):
|
|
if isinstance(value, dict):
|
|
for _obj, val in value.items():
|
|
if isinstance(val, dict):
|
|
for obj1, val1 in val.items():
|
|
if obj1 == "type" and val1 != "object":
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_vendor_list(val):
|
|
vendor_list = ""
|
|
if isinstance(val, list):
|
|
for vendor in val:
|
|
vendor_list = vendor if not vendor_list else (
|
|
vendor_list + "," + vendor)
|
|
return vendor_list
|
|
|
|
|
|
def get_option_value(value, option, default=None):
|
|
if isinstance(value, dict):
|
|
for obj, val in value.items():
|
|
if obj == option:
|
|
return val
|
|
return default
|
|
|
|
|
|
def get_param_type(value):
|
|
param_type = get_option_value(value, "type")
|
|
return Array_Types.get(param_type, None)
|
|
|
|
|
|
def get_description_from_json(value):
|
|
description = get_option_value(value, "description", "")
|
|
return description
|
|
|
|
|
|
def get_range_from_json(value):
|
|
range_value = get_option_value(value, "range", [])
|
|
return range_value
|
|
|
|
|
|
def get_list_from_json(value):
|
|
list_value = get_option_value(value, "list", {})
|
|
return list_value
|
|
|
|
|
|
def get_enum_from_json(value):
|
|
enumerations = get_option_value(value, "enumerations", [])
|
|
return enumerations
|
|
|
|
|
|
def is_proto_exist(value, proto):
|
|
protocols = get_option_value(value, "protocols", [])
|
|
return proto in protocols
|
|
|
|
|
|
def clear_list(input_list):
|
|
input_list.clear()
|
|
|
|
|
|
def generate_shared_library(output_library, source_files, vendor_prefix, extra_dependencies):
|
|
# Return if source_files (list) is empty
|
|
if len(source_files) == 0:
|
|
return
|
|
|
|
# Set vendor prefix
|
|
if vendor_prefix is not None:
|
|
VENDOR_PREFIX = vendor_prefix
|
|
else:
|
|
VENDOR_PREFIX = "X_IOPSYS_EU_"
|
|
|
|
# Ensure that the source files exist
|
|
for source_file in source_files:
|
|
if not os.path.exists(source_file):
|
|
print(f" Error: Source file {source_file} does not exist.")
|
|
return False
|
|
|
|
cmd = ['gcc', '-shared', '-o', output_library, '-fPIC', '-DBBF_VENDOR_PREFIX=\\"{}\\"'.format(VENDOR_PREFIX)] + source_files + extra_dependencies
|
|
# Compile the shared library
|
|
try:
|
|
cmdstr = ' '.join(str(e) for e in cmd)
|
|
subprocess.run(cmdstr, shell=True, check=True)
|
|
print(f" Shared library {output_library} successfully created.")
|
|
return True
|
|
except subprocess.CalledProcessError as e:
|
|
print(f" Error during compilation: {e}")
|
|
return False
|
|
|
|
|
|
def build_and_install_bbfdm(vendor_prefix, vendor_list):
|
|
print("Compiling and installing bbfdmd in progress ...")
|
|
|
|
create_folder(os.path.join(CURRENT_PATH, "build"))
|
|
cd_dir(os.path.join(CURRENT_PATH, "build"))
|
|
|
|
# Set vendor prefix
|
|
if vendor_prefix is not None:
|
|
VENDOR_PREFIX = vendor_prefix
|
|
else:
|
|
VENDOR_PREFIX = "X_IOPSYS_EU_"
|
|
|
|
# Set vendor list
|
|
if vendor_list is None:
|
|
VENDOR_LIST = "iopsys"
|
|
else:
|
|
VENDOR_LIST = get_vendor_list(vendor_list)
|
|
|
|
# Build and install bbfdm
|
|
cmake_command = [
|
|
"cmake",
|
|
"../",
|
|
"-DWITH_OPENSSL=ON",
|
|
"-DBBF_VENDOR_EXTENSION=ON",
|
|
"-DBBF_SCHEMA_FULL_TREE=ON",
|
|
f"-DBBF_VENDOR_LIST={VENDOR_LIST}",
|
|
f"-DBBF_VENDOR_PREFIX={VENDOR_PREFIX}",
|
|
"-DBBF_MAX_OBJECT_INSTANCES=255",
|
|
"-DBBFDMD_MAX_MSG_LEN=1048576",
|
|
"-DCMAKE_INSTALL_PREFIX=/"
|
|
]
|
|
make_command = ["make"]
|
|
make_install_command = ["make", "install"]
|
|
|
|
try:
|
|
subprocess.check_call(cmake_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
subprocess.check_call(make_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
subprocess.check_call(make_install_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Error running commands: {e}")
|
|
sys.exit(1)
|
|
|
|
cd_dir(CURRENT_PATH)
|
|
remove_folder(os.path.join(CURRENT_PATH, "build"))
|
|
print('Compiling and installing bbfdmd done')
|
|
|
|
|
|
def run_command(command):
|
|
try:
|
|
# Use subprocess.Popen to start the daemon process
|
|
subprocess.Popen(command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
|
|
# The daemon process will continue running in the background
|
|
time.sleep(1)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
# Handle subprocess errors here
|
|
print(f"Error running the daemon process: {e}")
|
|
sys.exit(1)
|
|
|
|
|
|
def create_input_json_file(file_path, input_type, input_name, micro_service_config):
|
|
data = {
|
|
"daemon": {
|
|
"input": {
|
|
"type": input_type,
|
|
"name": input_name
|
|
},
|
|
"output": {
|
|
"type": "UBUS",
|
|
"name": micro_service_config.get("name"),
|
|
"parent_dm": micro_service_config.get("parent_dm"),
|
|
"object": micro_service_config.get("object"),
|
|
"root_obj": micro_service_config.get("root_obj")
|
|
}
|
|
}
|
|
}
|
|
|
|
with open(file_path, 'w', encoding='utf-8') as json_file:
|
|
json.dump(data, json_file, indent=4)
|
|
|
|
|
|
def transform_schema_to_dm(schema_out, dm_list):
|
|
if not schema_out or not isinstance(schema_out, list) or len(schema_out) == 0:
|
|
return
|
|
|
|
result_list = schema_out[0].get("results", [])
|
|
|
|
for result in result_list:
|
|
path = result.get("path", "")
|
|
data = result.get("data", "0")
|
|
permission = "readOnly" if data == "0" else "readWrite"
|
|
p_type = result.get("type", "xsd:string")[4:]
|
|
|
|
entry = {
|
|
"param": path,
|
|
"permission": permission,
|
|
"type": p_type,
|
|
}
|
|
|
|
dm_list.append(entry)
|
|
|
|
|
|
def remove_duplicate_elements(input_list):
|
|
unique_values = set()
|
|
result_list = []
|
|
|
|
for item in input_list:
|
|
item_value = item["param"]
|
|
if item_value not in unique_values:
|
|
unique_values.add(item_value)
|
|
result_list.append(item)
|
|
|
|
return result_list
|
|
|
|
|
|
def fill_list_supported_dm():
|
|
# Wait for 5 seconds to be sure that all micro-services started successfully
|
|
time.sleep(5)
|
|
|
|
# pylint: disable=E1101
|
|
ubus.connect()
|
|
|
|
usp_schema_out = ubus.call('bbfdm', 'schema', {"path": "Device.", "optional": {"proto": "usp"}})
|
|
transform_schema_to_dm(usp_schema_out, LIST_SUPPORTED_USP_DM)
|
|
LIST_SUPPORTED_USP_DM.sort(key=lambda x: x['param'], reverse=False)
|
|
LIST_SUPPORTED_USP_DM[:] = remove_duplicate_elements(LIST_SUPPORTED_USP_DM)
|
|
|
|
cwmp_schema_out = ubus.call('bbfdm', 'schema', {"path": "Device.", "optional": {"proto": "cwmp"}})
|
|
transform_schema_to_dm(cwmp_schema_out, LIST_SUPPORTED_CWMP_DM)
|
|
LIST_SUPPORTED_CWMP_DM.sort(key=lambda x: x['param'], reverse=False)
|
|
LIST_SUPPORTED_CWMP_DM[:] = remove_duplicate_elements(LIST_SUPPORTED_CWMP_DM)
|
|
|
|
ubus.disconnect()
|
|
# pylint: enable=E1101
|
|
|
|
|
|
def get_micro_service_config(micro_service):
|
|
parent_dm = get_option_value(micro_service, "parent_dm")
|
|
root_obj = get_option_value(micro_service, "root_obj")
|
|
obj = get_option_value(micro_service, "object")
|
|
name = get_option_value(micro_service, "name")
|
|
|
|
if not isinstance(micro_service, dict) or None in (parent_dm, root_obj, obj, name):
|
|
return None
|
|
|
|
return {
|
|
"parent_dm": parent_dm,
|
|
"root_obj": root_obj,
|
|
"object": obj,
|
|
"name": name
|
|
}
|
|
|
|
|
|
def clone_git_repository(repo, version=None):
|
|
try:
|
|
cmd = ["git", "clone", repo, ".repo"]
|
|
if version is not None:
|
|
cmd.extend(["-b", version])
|
|
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
|
return True
|
|
except (OSError, subprocess.SubprocessError):
|
|
print(f' Failed to clone {repo} !!!!!')
|
|
return False
|
|
|
|
|
|
def get_repo_version_info(repo, version=None):
|
|
if version is None:
|
|
return repo
|
|
return f'{repo}^{version}'
|
|
|
|
|
|
def process_json_file(filename, idx, micro_service, micro_service_config):
|
|
if micro_service is None:
|
|
move_file(filename, "/etc/bbfdm/plugins")
|
|
else:
|
|
micro_srv_path = f"/etc/app{idx}"
|
|
micro_srv_lib_path = f"{micro_srv_path}/file{idx}.json"
|
|
micro_srv_input_path = f"{micro_srv_path}/input.json"
|
|
create_folder(micro_srv_path)
|
|
move_file(filename, micro_srv_lib_path)
|
|
create_input_json_file(micro_srv_input_path, "JSON", micro_srv_lib_path, micro_service_config)
|
|
run_command(f"/usr/sbin/bbfdmd -m {micro_srv_input_path}")
|
|
|
|
|
|
def process_c_files(LIST_FILES, vendor_prefix, extra_dependencies, idx, micro_service, micro_service_config):
|
|
if micro_service is None:
|
|
generate_shared_library(f"/etc/bbfdm/plugins/lib{idx}.so", LIST_FILES, vendor_prefix, extra_dependencies)
|
|
else:
|
|
micro_srv_path = f"/etc/app{idx}"
|
|
micro_srv_lib_path = f"{micro_srv_path}/lib{idx}.so"
|
|
micro_srv_input_path = f"{micro_srv_path}/input.json"
|
|
create_folder(micro_srv_path)
|
|
generate_shared_library(micro_srv_lib_path, LIST_FILES, vendor_prefix, extra_dependencies)
|
|
create_input_json_file(micro_srv_input_path, "DotSo", micro_srv_lib_path, micro_service_config)
|
|
run_command(f"/usr/sbin/bbfdmd -m {micro_srv_input_path}")
|
|
|
|
|
|
def download_and_build_plugins(plugins, vendor_prefix):
|
|
global BBF_ERROR_CODE
|
|
|
|
if plugins is None or not isinstance(plugins, list) or not plugins:
|
|
print("No plugins provided.")
|
|
return
|
|
|
|
print("Generating data models from defined plugins...")
|
|
|
|
for plugin_index, plugin in enumerate(plugins):
|
|
micro_service_config = None
|
|
|
|
repo = get_option_value(plugin, "repo")
|
|
dm_files = get_option_value(plugin, "dm_files")
|
|
extra_dependencies = get_option_value(plugin, "extra_dependencies", [])
|
|
|
|
if repo is None or dm_files is None or not isinstance(dm_files, list):
|
|
print("Necessary input missing")
|
|
BBF_ERROR_CODE += 1
|
|
continue
|
|
|
|
micro_service = get_option_value(plugin, "micro-service")
|
|
if micro_service is not None:
|
|
micro_service_config = get_micro_service_config(micro_service)
|
|
if micro_service_config is None:
|
|
print("Micro service config not defined")
|
|
BBF_ERROR_CODE += 1
|
|
continue
|
|
|
|
print(f' - Processing plugin: {plugin}')
|
|
|
|
version = get_option_value(plugin, "version")
|
|
|
|
remove_folder(".repo")
|
|
|
|
if not clone_git_repository(repo, version):
|
|
print(f"Failed to clone {repo}")
|
|
BBF_ERROR_CODE += 1
|
|
continue
|
|
|
|
print(f' Processing {get_repo_version_info(repo, version)}')
|
|
|
|
LIST_FILES = []
|
|
os.chdir(".repo/")
|
|
for dm_file in dm_files:
|
|
filename = dm_file
|
|
if filename.endswith('*.c'):
|
|
LIST_FILES.extend(glob.glob(filename))
|
|
else:
|
|
if os.path.isfile(filename):
|
|
if filename.endswith('.c'):
|
|
LIST_FILES.append(filename)
|
|
elif filename.endswith('.json'):
|
|
process_json_file(filename, plugin_index, micro_service, micro_service_config)
|
|
else:
|
|
print(f"Unknown file format {filename}")
|
|
BBF_ERROR_CODE += 1
|
|
else:
|
|
print(f"File not accessible {filename}")
|
|
BBF_ERROR_CODE += 1
|
|
|
|
if len(LIST_FILES) > 0:
|
|
process_c_files(LIST_FILES, vendor_prefix, extra_dependencies, plugin_index, micro_service, micro_service_config)
|
|
|
|
clear_list(LIST_FILES)
|
|
os.chdir("..")
|
|
|
|
remove_folder(".repo")
|
|
|
|
print('Generating plugins completed.')
|
|
|
|
|
|
def generate_supported_dm(vendor_prefix=None, vendor_list=None, plugins=None):
|
|
'''
|
|
Generates supported data models and performs necessary actions.
|
|
|
|
Args:
|
|
vendor_prefix (str, optional): Vendor prefix for shared libraries.
|
|
vendor_list (list, optional): List of vendor data models.
|
|
plugins (list, optional): List of plugin configurations.
|
|
'''
|
|
|
|
# Build && Install bbfdm
|
|
build_and_install_bbfdm(vendor_prefix, vendor_list)
|
|
|
|
# Download && Build Plugins Data Models
|
|
download_and_build_plugins(plugins, vendor_prefix)
|
|
|
|
# Run bbfdm daemon
|
|
run_command("/usr/sbin/bbfdmd")
|
|
|
|
# Fill the list supported data model
|
|
fill_list_supported_dm()
|
|
|
|
# kill all related bbfdm daemon
|
|
run_command("kill -9 $(pidof bbfdmd)")
|
|
|