From 2d94635b0ad5f647086a6f0e74e13c7438e9b271 Mon Sep 17 00:00:00 2001 From: Martin Skorupski Date: Thu, 13 Feb 2025 14:39:19 +0100 Subject: [PATCH] clean-up solution - explain usage of setup.sh - automatically discovert IP Address and interface Issue-ID: OAM-428 Change-Id: I548ac98cf0485fc2b84704d37371a095f4f37c03 Signed-off-by: Martin Skorupski --- solution/README.md | 90 +++++++++++++++++++----------------- solution/adopt_to_environment.py | 94 +++++++++++++++++++++++++++----------- solution/infra/.env | 2 + solution/infra/docker-compose.yaml | 6 +-- solution/network/.env | 4 +- solution/requirements.txt | 26 +++++++++++ solution/setup.sh | 31 ++++++++++++- solution/teardown.sh | 21 +++++++++ 8 files changed, 197 insertions(+), 77 deletions(-) create mode 100644 solution/requirements.txt diff --git a/solution/README.md b/solution/README.md index 22ca79f..9d89613 100644 --- a/solution/README.md +++ b/solution/README.md @@ -1,13 +1,13 @@ -# Service Management and Orchestration (SMO) +# Service Management and Orchestration (SMO) for OAM This project focus on a docker-compose deployment solution for SMO/OAM Components. ## Introduction -With respect to Operation and Maintenance (OAM) the SMO implements the O1-interface consumers. -According to the O-RAN OAM Architecture and the O-RAN OAM Interface Specification, -the SMO implements a NETCONF Client for configuration and a HTTP/REST/VES server -for receiving all kind of events in VES format. +With respect to Operation and Maintenance (OAM) the SMO implements the O1-interface and OpenFronthaul Management-Plane consumers. +According to the O-RAN OAM Architecture and the O-RAN OAM Interface Specification, the SMO implements a NETCONF Client for configuration +and a HTTP/REST/VES server for receiving all kind of events in +VES format. The O-RAN-SC OAM deployment contains an OpenDaylight based NETCONF client and an ONAP VES Collector. Kafka is used as massage router for communication between the components. The Keycloak implementation offers and Identity service, while traefik acts as reverse proxy to terminate all incoming https traffic. For storing data in a persistence way the implementation of the mariaDB project is used. @@ -33,6 +33,9 @@ with the following components. * **Messages** ... representing SMO MessageRouter component, includes message-router + * **Gateway** + ... representing a revers proxy terminating TLS traffic (https, NETCONF) + ## Prerequisites ### Resources @@ -54,7 +57,7 @@ PRETTY_NAME="Ubuntu 24.04.1 LTS" ``` $ docker --version -Docker version 27.3.1, build ce12230 +Docker version 27.5.1, build 9f9e405 ``` Please follow the required docker daemon configuration as documented in the following README.md: - [./smo/common/docker/README.md](./smo/common/docker/README.md) @@ -63,7 +66,7 @@ Please follow the required docker daemon configuration as documented in the foll ``` $ docker compose version -Docker Compose version v2.29.7 +Docker Compose version v2.32.4 ``` ### GIT @@ -84,43 +87,51 @@ A python parser package is required. ``` python3 -m venv .oam source .oam/bin/activate -pip3 install jproperties -pip3 install jinja2 -pip3 install requests +pip3 install requirements.txt +``` + +### ETC Host (DNS function) -# your system IP is required in .env files, please see explanations below -python3 ./adopt_to_environment.py -i +Your local IP and your used interface is required. Please use the following +script to modify all .env and other configuration files accordingly. +The script will find automatically the interface and its ip-address +to the internet. +You can check its usage with option "-h": + +``` +python3 ./adopt_to_environment.py -h ``` -It is beneficial (but not mandatory) adding the following line add the -end of your ~/.bashrc file. I will suppress warnings when python script -do not verify self signed certificates for HTTPS communication. +Please run the script with your preferred fully qualified domain name, +you like to use in your browser address bar. ``` -export PYTHONWARNINGS="ignore:Unverified HTTPS request" +python3 ./adopt_to_environment.py -d ``` -### ETC Host (DNS function) +you can revert the settings in the modified .env and configuration files +using the option "-r". -Please change in the different .env files the environment variable 'HOST_IP' -to the IP address of the system where you deploy the solution - search for -'aaa.bbb.ccc.dd' and replace it. +``` +python3 ./adopt_to_environment.py -d -r +``` +It is beneficial (but not mandatory) adding the following line add the +end of your ~/.bashrc file. I will suppress warnings when python script +do not verify self signed certificates for HTTPS communication. + +``` +export PYTHONWARNINGS="ignore:Unverified HTTPS request" ``` -# replace xxx.yyy.zzz.www by your routable IP address -grep -arl --include=*\.env 'aaa.bbb.ccc.dd' * | while read -r file; do - sed -i 's/aaa.bbb.ccc.dd/xxx.yyy.zzz.www/g' "$file" -done -`` -Please modify the /etc/hosts of your system. +Please modify the /etc/hosts of your system or modify the DNS of your +environment. * \: is the hostname of the system, where the browser is started * \: is the IP address of the system where the solution will be deployed -For development purposes and may reference the same system. ``` $ cat /etc/hosts @@ -152,20 +163,7 @@ next chapters. ```bash source .oam/bin/activate -docker compose -f infra/docker-compose.yaml up -d -docker compose -f smo/common/docker-compose.yaml up -d - -# optionally adjust the users.csv file to create new users -vim users.csv -# override authentication.json with the new users -python3 create_users.py users.csv -o smo/common/identity/authentication.json - -python3 smo/common/identity/config.py - -docker compose -f smo/oam/docker-compose.yaml up -d -docker compose -f smo/apps/docker-compose.yaml up -d - -# the cpu load is low again, we can start a simulated network +./setup.sh ``` #### Simulated network @@ -201,6 +199,7 @@ before starting further docker images. The several docker-compose yaml files must be started in the right order as listed below: ``` +docker compose -f infra/docker-compose.yaml up -d docker compose -f smo/common/docker-compose.yaml up -d python smo/common/identity/config.py ``` @@ -208,7 +207,6 @@ python smo/common/identity/config.py The python script configure the users within the identity service (keycloak). A system user (%USER) is also created with administration rights. - ``` docker compose -f smo/oam/docker-compose.yaml up -d ``` @@ -293,6 +291,12 @@ docker compose -f network/docker-compose.yaml down docker compose -f smo/apps/docker-compose.yaml down docker compose -f smo/oam/docker-compose.yaml down docker compose -f smo/common/docker-compose.yaml down +docker compose -f infra/docker-compose.yaml down +``` + +alternative: +``` +./teardown.sh ``` ### Cleanup @@ -311,5 +315,5 @@ Please make sure that the network settings to not overlap with other networks. The commands ... ``` docker ps -a -docker-compose ps +docker compose ps docker rm -f $(docker ps -aq) diff --git a/solution/adopt_to_environment.py b/solution/adopt_to_environment.py index 5dade68..7dce99f 100755 --- a/solution/adopt_to_environment.py +++ b/solution/adopt_to_environment.py @@ -1,6 +1,5 @@ -#!/usr/bin/env python3 ################################################################################ -# Copyright 2024 highstreet technologies +# Copyright 2025 highstreet technologies USA Corp. # # Licensed under the Apache License, Version 2.0 (the 'License'); # you may not use this file except in compliance with the License. @@ -15,10 +14,15 @@ # limitations under the License. # +#!/usr/bin/env python3 + import os +import socket +import netifaces import argparse from jinja2 import Template +default_interface = "eth0" default_ip_address = 'aaa.bbb.ccc.ddd' default_http_domain = 'smo.o-ran-sc.org' @@ -28,8 +32,6 @@ file_extensions = ['.env', '.yaml', '.json'] parser = argparse.ArgumentParser(script_name) required = parser.add_argument_group('required named arguments') -required.add_argument("-i", "--ip_address", help="The remote accessible IP address of this system.", - type=str, required=True) parser.add_argument("-d", "--http_domain", help="The http domain. Default is " + default_http_domain + ".", type=str, default=default_http_domain) @@ -43,14 +45,19 @@ def find_replace(directory, find_text, replace_text, extensions): file_path = os.path.join(root, file_name) file_ext = os.path.splitext(file_name)[1] if file_ext in extensions or file_name in extensions: - with open(file_path, 'r') as file: - content = file.read() - if find_text in content: - updated_content = content.replace(find_text, replace_text) - with open(file_path, 'w') as file: - file.write(updated_content) - print(f"Replaced '{find_text}' with '{replace_text}' in '{file_path}'") - + try: + with open(file_path, 'r') as file: + content = file.read() + if find_text in content: + updated_content = content.replace(find_text, replace_text) + with open(file_path, 'w') as file: + file.write(updated_content) + print(f"Replaced '{find_text}' with '{replace_text}' in '{file_path}'") + except PermissionError: + # Ignore or handle as you wish: + # e.g., just print a warning or do nothing + # print(f"Warning: Could not open {file_path} for writing (permission denied).") + pass def create_etc_hosts(ip_address_v4: str, http_domain: str ) -> None: """ @@ -65,6 +72,7 @@ def create_etc_hosts(ip_address_v4: str, http_domain: str ) -> None: {{ deployment_system_ipv4 }} identity.{{ http_domain }} {{ deployment_system_ipv4 }} messages.{{ http_domain }} {{ deployment_system_ipv4 }} kafka-bridge.{{ http_domain }} +{{ deployment_system_ipv4 }} kafka-ui.{{ http_domain }} {{ deployment_system_ipv4 }} odlux.oam.{{ http_domain }} {{ deployment_system_ipv4 }} flows.oam.{{ http_domain }} {{ deployment_system_ipv4 }} tests.oam.{{ http_domain }} @@ -78,22 +86,54 @@ def create_etc_hosts(ip_address_v4: str, http_domain: str ) -> None: output_txt_path = f"{directory_path}/append_to_etc_hosts.txt" with open(output_txt_path, 'w', encoding="utf-8") as f: f.write(hosts_entries) - print(f"/etc/hosts entries created: {output_txt_path}") + print(f"Entries for /etc/hosts: {output_txt_path}") + +def get_default_interface(): + """Return the name of the default interface for IPv4 traffic.""" + gws = netifaces.gateways() + # gws['default'] might look like {AF_INET: ('192.168.1.1', 'eth0')} + if 'default' in gws and netifaces.AF_INET in gws['default']: + # The tuple is (gateway_ip, interface_name) + _, interface = gws['default'][netifaces.AF_INET] + return interface + return None + +def get_interface_ipv4_addr(interface): + """Return the IPv4 address for a specified interface using netifaces.""" + addrs = netifaces.ifaddresses(interface) + if netifaces.AF_INET in addrs: + return addrs[netifaces.AF_INET][0].get('addr') + return None + +if __name__ == "__main__": + interface = get_default_interface() + print("Default interface:", interface) + + if interface: + ip_address = get_interface_ipv4_addr(interface) + print("IPv4 on default interface:", ip_address) + + if args.revert == False: + # replace interface + find_replace(directory_path, default_interface, interface, file_extensions) + + # replace ip + find_replace(directory_path, default_ip_address, ip_address, file_extensions) + + # replace domain + if not args.http_domain == default_http_domain: + find_replace(directory_path, default_http_domain, args.http_domain, file_extensions) -if args.revert == False: - # replace ip - find_replace(directory_path, default_ip_address, args.ip_address, file_extensions) + # write append file for etc/hosts + create_etc_hosts(ip_address_v4=ip_address, http_domain=args.http_domain) - # replace domain - if not args.http_domain == default_http_domain: - find_replace(directory_path, default_http_domain, args.http_domain, file_extensions) - # write append file for etc/hosts - create_etc_hosts(ip_address_v4=args.ip_address, http_domain=args.http_domain) -else: - # revert back ip - find_replace(directory_path, args.ip_address, default_ip_address, file_extensions) + else: + # revert back interface + find_replace(directory_path, interface, default_interface, file_extensions) - # revert back domain - if not args.http_domain == default_http_domain: - find_replace(directory_path, args.http_domain, default_http_domain, file_extensions) + # revert back ip + find_replace(directory_path, ip_address, default_ip_address, file_extensions) + # revert back domain + if not args.http_domain == default_http_domain: + find_replace(directory_path, args.http_domain, default_http_domain, file_extensions) diff --git a/solution/infra/.env b/solution/infra/.env index a5a7d2e..6a3f395 100644 --- a/solution/infra/.env +++ b/solution/infra/.env @@ -14,6 +14,8 @@ # limitations under the License. # +INTERFACE_NAME=eth0 + NETWORK_SUBNET_DHCP_IPv4=172.99.0.0/16 NETWORK_GATEWAY_DHCP_IPv4=172.99.0.1 NETWORK_DHCP_CONTAINER_IPv4=172.99.0.10 \ No newline at end of file diff --git a/solution/infra/docker-compose.yaml b/solution/infra/docker-compose.yaml index cc715d5..6f2f0eb 100644 --- a/solution/infra/docker-compose.yaml +++ b/solution/infra/docker-compose.yaml @@ -1,5 +1,5 @@ ################################################################################ -# Copyright 2025 highstreet technologies +# Copyright 2025 highstreet technologies USA Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -50,10 +50,10 @@ networks: name: dhcp driver: macvlan driver_opts: - parent: ens34 # or whichever host interface you want + parent: ${INTERFACE_NAME} # or whichever host interface you want macvlan_mode: bridge ipam: config: - - subnet: ${NETWORK_SUBNET_DCN_IPv4} + - subnet: ${NETWORK_SUBNET_DHCP_IPv4} gateway: ${NETWORK_GATEWAY_DHCP_IPv4} diff --git a/solution/network/.env b/solution/network/.env index b91a382..17ea203 100644 --- a/solution/network/.env +++ b/solution/network/.env @@ -1,5 +1,5 @@ ################################################################################ -# Copyright 2021 highstreet technologies and others +# Copyright 2025 highstreet technologies USA Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ VES_ENDPOINT_PASSWORD=sample1 NEXUS3_DOCKER_REPO=nexus3.o-ran-sc.org:10004/o-ran-sc/ LOCAL_DOCKER_REPO= -PYNTS_VERSION=0.8.1 +PYNTS_VERSION=latest NETCONF_USERNAME=netconf NETCONF_PASSWORD=netconf! diff --git a/solution/requirements.txt b/solution/requirements.txt new file mode 100644 index 0000000..660ceb9 --- /dev/null +++ b/solution/requirements.txt @@ -0,0 +1,26 @@ +################################################################################ +# Copyright 2025 highstreet technologies USA Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + +certifi==2024.8.30 +charset-normalizer==3.4.0 +idna==3.10 +Jinja2==3.1.4 +jproperties==2.1.2 +MarkupSafe==3.0.2 +netifaces==0.11.0 +requests==2.32.3 +six==1.16.0 +urllib3==2.2.3 diff --git a/solution/setup.sh b/solution/setup.sh index e434fa0..aeaf87e 100755 --- a/solution/setup.sh +++ b/solution/setup.sh @@ -1,11 +1,38 @@ +################################################################################ +# Copyright 2025 highstreet technologies USA Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + #!/bin/bash SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -docker compose -f $SCRIPT_DIR/smo/common/docker-compose.yaml up -d --wait -python3 create_users.py $SCRIPT_DIR/users.csv -o $SCRIPT_DIR/smo/common/identity/authentication.json +cat /etc/os-release | grep PRETTY_NAME +docker --version +docker compose version +python3 --version + +docker compose -f $SCRIPT_DIR/infra/docker-compose.yaml up -d +docker compose -f $SCRIPT_DIR/smo/common/docker-compose.yaml up -d python3 $SCRIPT_DIR/smo/common/identity/config.py docker compose -f $SCRIPT_DIR/smo/oam/docker-compose.yaml up -d +# docker compose -f $SCRIPT_DIR/smo/apps/docker-compose.yaml up -d + +# simulated network - once manually build +# docker compose -f network/docker-compose.yaml up -d +# docker compose -f network/docker-compose.yaml restart pynts-o-du-o1 + diff --git a/solution/teardown.sh b/solution/teardown.sh index 0e5340a..0e50cae 100755 --- a/solution/teardown.sh +++ b/solution/teardown.sh @@ -1,5 +1,26 @@ +################################################################################ +# Copyright 2025 highstreet technologies USA Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# + #!/bin/bash SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +docker compose -f $SCRIPT_DIR/network/docker-compose.yaml down +docker compose -f $SCRIPT_DIR/smo/apps/docker-compose.yaml down docker compose -f $SCRIPT_DIR/smo/oam/docker-compose.yaml down docker compose -f $SCRIPT_DIR/smo/common/docker-compose.yaml down +docker compose -f $SCRIPT_DIR/infra/docker-compose.yaml up down + -- 2.16.6