-# 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.
* **Messages**
... representing SMO MessageRouter component, includes message-router
+ * **Gateway**
+ ... representing a revers proxy terminating TLS traffic (https, NETCONF)
+
## Prerequisites
### Resources
```
$ 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)
```
$ docker compose version
-Docker Compose version v2.29.7
+Docker Compose version v2.32.4
```
### GIT
```
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 <deployment-system-ipv4>
+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 <your-smo-fqdn>
```
-### 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 <your-smo-fqdn> -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.
* \<your-system>: is the hostname of the system, where the browser is started
* \<deployment-system-ipv4>: is the IP address of the system where the solution will be deployed
-For development purposes <your-system> and <deployment-system> may reference the same system.
```
$ cat /etc/hosts
```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
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
```
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
```
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
The commands ...
```
docker ps -a
-docker-compose ps
+docker compose ps
docker rm -f $(docker ps -aq)
-#!/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.
# 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'
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)
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:
"""
{{ 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 }}
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)
# 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
################################################################################
-# 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.
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}
################################################################################
-# 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.
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!
--- /dev/null
+################################################################################
+# 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
+################################################################################
+# 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
+
+################################################################################
+# 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
+