├── .env ├── .gitignore ├── DEBUG.md ├── LICENSE ├── README.md ├── adaptive_monitoring ├── README.md ├── collect_dataset.py ├── data │ ├── README.md │ └── cloud_gaming │ │ ├── slice_throughput.csv │ │ ├── smf_cpu_usage.csv │ │ ├── upf_cpu_usage.csv │ │ ├── upf_energy_usage.csv │ │ ├── upf_mem_usage.csv │ │ └── upf_scrape_duration.csv ├── main.py ├── requirements.txt ├── settings.yaml └── src │ ├── config.py │ ├── sampling.py │ ├── utils.py │ └── visualization.py ├── bin ├── check-inotify.py ├── clear_cni.sh ├── inotify-consumers.sh ├── k8s-log.sh ├── k8s-resources.sh ├── k8s-shell.sh ├── k8s-ue-pinger.sh └── print-nodeports.sh ├── data_distribution ├── README.md ├── install.sh ├── test-data-distribution.sh ├── thanos-values.yaml └── uninstall.sh ├── data_store ├── README.md ├── install-mc.sh ├── install.sh ├── minio │ └── values.yaml ├── mongodb │ ├── kustomization.yaml │ ├── mongodb-deployment.yaml │ ├── mongodb-service.yaml │ └── mongodb-serviceaccount.yaml ├── test-data-store.sh └── uninstall.sh ├── data_visualization ├── dashboards │ └── monarch-dashboard.json ├── install.sh ├── kustomization.yaml ├── test-data-visualization.sh ├── uninstall.sh └── values.yaml ├── deploy-all.sh ├── deploy-external.sh ├── deploy-monarch-core.sh ├── deploy-monarch-nss.sh ├── get-node-ip.sh ├── images ├── cni-out-of-ips.png ├── core-test-2.png ├── data-distribution-up.png ├── data-source.png ├── data-store-up.png ├── datasource-config-1.png ├── datasource-config-2.png ├── dataviz-gui.png ├── dataviz-up.png ├── deploying-ueransim.png ├── external-components.png ├── grafana-edit-1.png ├── grafana-edit-2.png ├── grafana-metrics-browser.png ├── minio-access.png ├── minio-keys.png ├── monarch-conceptual-architecture.png ├── monarch-core.png ├── monarch-dashboard-1.png ├── monarch-dashboard-2.png ├── monarch-dashboard.png ├── monitoring-directives.png ├── monitoring-manager-up.png ├── network-up.png ├── nssdc-up.png ├── prometheus-crash-1.png ├── prometheus-tsdb.png ├── request-success.png ├── request-translator-up.png ├── rogers-demo-dashboard.png ├── test-external.png ├── test-monarch-core.png ├── test-monarch-nss.png ├── thanos-stores.png └── watch-monarch-core.png ├── kpi_computation ├── README.md ├── check-kpi.sh ├── install.sh ├── otel │ ├── Dockerfile │ ├── app │ │ ├── kpi_calculator.py │ │ └── requirements.txt │ └── kpi_calculator.yaml ├── standard │ ├── Dockerfile │ ├── app │ │ ├── kpi_calculator.py │ │ └── requirements.txt │ └── kpi_calculator.yaml └── uninstall.sh ├── labs ├── images │ ├── 5g-targets.png │ ├── aggregration.png │ ├── beyond-monarch.png │ ├── deriv.png │ ├── expression-browser.png │ ├── instrumentationx.png │ ├── prometheus-1.png │ ├── prometheus-exporter-target.png │ ├── prometheus-interface.png │ ├── prometheus-logo.png │ ├── prometheus.png │ ├── promtheus-select-targets.png │ ├── promtheus-targets.png │ ├── queries-01.png │ ├── query-engine.png │ ├── query-enginex.png │ ├── thanos-query-api.png │ └── thanos.png ├── lab1 │ ├── Dockerfile │ ├── README.md │ ├── README.pdf │ ├── app │ │ ├── exporter.py │ │ └── requirements.txt │ ├── deployment.yaml │ └── service.yaml ├── lab2 │ ├── README.md │ └── README.pdf └── lab3 │ ├── README.md │ ├── README.pdf │ ├── exercise.py │ └── solution.py ├── mde ├── README.md ├── check-mde.sh ├── install.sh ├── metrics-service.yaml ├── otel │ ├── collector.yaml │ ├── kustomization.yaml │ ├── otel-deployment.yaml │ ├── otel-service.yaml │ └── otel-servicemonitor.yaml ├── standard │ └── metrics-servicemonitor.yaml └── uninstall.sh ├── monitoring_manager ├── Dockerfile ├── README.md ├── app │ ├── __init__.py │ ├── directive_manager.py │ ├── logger.py │ ├── monitoring_manager.py │ ├── orchestrator.py │ └── requirements.txt ├── install.sh ├── manifests │ ├── deployment.yaml │ └── service.yaml ├── run.py ├── test-monitoring-manager.sh └── uninstall.sh ├── nfv_orchestrator ├── README.md ├── install.sh ├── nfv-orchestrator.py ├── test-nfv-orchestrator.sh ├── uninstall.sh └── view-logs.sh ├── nssdc ├── README.md ├── additional-scrape-configs.yaml ├── install.sh ├── status.sh ├── test-nssdc.sh ├── thanos-objstore-config.yaml ├── uninstall.sh └── values.yaml ├── remove-all.sh ├── remove-external.sh ├── remove-monarch-core.sh ├── remove-monarch-nss.sh ├── replace-node-ip.sh ├── request_translator ├── Dockerfile ├── README.md ├── app │ ├── __init__.py │ ├── comm_manager.py │ ├── db_manager.py │ ├── kpi_manager.py │ ├── logger.py │ ├── request_translator.py │ ├── requirements.txt │ ├── schema.json │ ├── service_orchestrator.py │ ├── supported_kpis.json │ └── translation_manager.py ├── install.sh ├── manifests │ ├── deployment.yaml │ └── service.yaml ├── port-forward-mongodb.sh ├── requests │ ├── request_invalid.json │ ├── request_nf.json │ └── request_slice.json ├── run.py ├── slice_components.json ├── test-request-translator.sh ├── test_api.py └── uninstall.sh ├── requirements.txt ├── service_orchestrator ├── README.md ├── install.sh ├── service-orchestrator.py ├── slice_info.json ├── test-service-orchestrator.sh ├── uninstall.sh └── view-logs.sh ├── slides.md ├── slides.pdf ├── test-all.sh ├── test-external.sh ├── test-monarch-core.sh ├── test-monarch-nss.sh └── utils ├── cecho.sh ├── check_status.sh └── ubuntu-sleep.yaml /.env: -------------------------------------------------------------------------------- 1 | NODE_IP="" 2 | SERVICE_ORCHESTRATOR_URI="http://${NODE_IP}:5001" 3 | MONARCH_MINIO_ENDPOINT="${NODE_IP}:30712" 4 | MONARCH_MINIO_ACCESS_KEY="admin" 5 | MONARCH_MINIO_SECRET_KEY="monarch-operator" 6 | MONARCH_MONITORING_INTERVAL="1s" 7 | MONARCH_THANOS_STORE_GRPC="${NODE_IP}:30905" 8 | MONARCH_THANOS_STORE_HTTP="${NODE_IP}:30906" 9 | NFV_ORCHESTRATOR_URI="http://${NODE_IP}:6001" 10 | MONARCH_THANOS_URL="http://${NODE_IP}:31004" 11 | MONARCH_REQUEST_TRANSLATOR_URI="http://${NODE_IP}:30700" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | adaptive_monitoring/**/figures/ 163 | adaptive_monitoring/**/results/ 164 | monarch_querier/**/plots/ 165 | monarch_querier/**/data/ -------------------------------------------------------------------------------- /DEBUG.md: -------------------------------------------------------------------------------- 1 | # debugging 2 | 3 | ## prometheus crashloopbackoff 4 | 5 | ![prometheus crash #1](images/prometheus-crash-1.png) 6 | 7 | Caused by duplicate prometheus-kubelet services in kube-system. There should only be one. 8 | Note that helm chart uninstall is not clearing out this service. 9 | 10 | ## pods stuck in init 11 | Describe pod state. Check if cni says it is running out of IPs. 12 | 13 | ![cni out of IPs](images/cni-out-of-ips.png) 14 | 15 | Use the clear_cni.sh script to clear out the cni state. This will cause a restart of the cni pods and free up the IPs. 16 | 17 | ## multus crashloopbackoff 18 | https://github.com/k8snetworkplumbingwg/multus-cni/issues/710#issue-977244895 19 | Multus can get OOMKilled. 20 | Increase resources for multus pods. 21 | 22 | ## multus error after reboot 23 | 24 | ```bash 25 | sudo rm /opt/cni/bin/multus-shim 26 | ``` 27 | then reboot 28 | 29 | ## prometheus crashloopbackoff 30 | Prometheus can get OOMKilled if it is collecting too many series. 31 | 32 | ![prometheus-tsdb](images/prometheus-tsdb.png) 33 | 34 | Reduce high-cardinality metrics or increase resources for prometheus pods. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Niloy Saha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 5G-Monarch 2 | 3 | ![GitHub](https://img.shields.io/github/license/niloysh/5g-monarch) ![GitHub forks](https://img.shields.io/github/forks/niloysh/5g-monarch?style=social) 4 | 5 | **5G-Monarch** is a network slice monitoring architecture for cloud native 5G network deployments. This repository contains the source code and configuration files for setting up 5G-MonArch, in conjunction with a 5G network deployment. 6 | 7 | ![monarch-conceptual-architecture](images/monarch-conceptual-architecture.png) 8 | 9 | The figure above shows the conceptual architecture of Monarch. Monarch is designed for cloud-native 5G deployments and focuses on network slice monitoring and per-slice KPI computation. 10 | 11 | ## Downloads 12 | - [NOMS'23 Paper](https://niloysh.github.io/papers/conferences/2023-noms-monarch.pdf) 13 | - [TNSM'24 Paper](https://niloysh.github.io/papers/journals/2024-tnsm-monarch.pdf) 14 | - [KPI Monitoring Video](https://www.youtube.com/watch?v=pIMBCwPs0wc) 15 | 16 | ## Table of Contents 17 | - [5G-Monarch](#5g-monarch) 18 | - [Downloads](#downloads) 19 | - [Table of Contents](#table-of-contents) 20 | - [Hardware Requirements](#hardware-requirements) 21 | - [Prerequisites](#prerequisites) 22 | - [Step 1: Deploy a 5G Network with Network Slicing Support](#step-1-deploy-a-5g-network-with-network-slicing-support) 23 | - [Step 2: Verify Network Slice Deployment](#step-2-verify-network-slice-deployment) 24 | - [Quick Start](#quick-start) 25 | - [Detailed Deployment Guide](#detailed-deployment-guide) 26 | - [Visualizing network slices KPIs using Monarch](#visualizing-network-slices-kpis-using-monarch) 27 | - [Citation](#citation) 28 | - [Contributions](#contributions) 29 | 30 | # Hardware Requirements 31 | - Supported OS: **Ubuntu 22.04 LTS** (recommended) or Ubuntu 20.04 LTS 32 | - Minimum Specifications: **8 cores, 16 GB RAM** 33 | 34 | 35 | # Prerequisites 36 | 37 | ### Step 1: Deploy a 5G Network with Network Slicing Support 38 | 39 | The [open5gs-k8s](https://github.com/niloysh/open5gs-k8s) repository contains the source code and configuration files for deploying a 5G network using Open5GS on Kubernetes. Please follow the detailed instructions in the open5gs-k8s repository to set up your 5G network. 40 | 41 | > [!NOTE] 42 | > To enable metrics collection for monitoring purposes, select the [Deployment with Monarch](https://github.com/niloysh/open5gs-k8s?tab=readme-ov-file#2-deployment-with-monarch-for-monitoring) option while deploying open5gs-k8s. 43 | 44 | ### Step 2: Verify Network Slice Deployment 45 | 46 | After deploying the 5G network, ensure that two network slices have been successfully configured by performing a [ping test to verify connectivity](https://github.com/niloysh/open5gs-k8s?tab=readme-ov-file#step-5-test-connectivity). This step confirms that the network is functioning correctly and is ready for Monarch deployment. 47 | 48 | # Quick Start 49 | To quickly set up the Monarch system, follow these steps: 50 | 51 | ```bash 52 | git clone https://github.com/niloysh/5g-monarch.git 53 | cd 5g-monarch 54 | ./deploy-all.sh 55 | ``` 56 | This script will deploy all core and external components needed for Monarch in a single step. 57 | 58 | ## Detailed Deployment Guide 59 | For a step-by-step deployment process, including individual component setup and configuration, refer to the [deployment slides](https://niloysh.github.io/5g-monarch/slides.pdf). 60 | 61 | # Visualizing network slices KPIs using Monarch 62 | 63 | The dashboard below shows Monarch being used to monitor network slices for a cloud-gaming use-case during a [demo at the University of Waterloo](https://uwaterloo.ca/news/researching-cutting-edge-5g-network-slicing-technology). 64 | 65 | ![rogers-demo-dashboard](images/rogers-demo-dashboard.png) 66 | 67 | # Citation 68 | ![GitHub](https://img.shields.io/badge/IEEE%20NOMS-2023-green) 69 | 70 | If you use the code in this repository in your research work or project, please consider citing the following publication. 71 | 72 | > N. Saha, N. Shahriar, R. Boutaba and A. Saleh. (2023). MonArch: Network Slice Monitoring Architecture for Cloud Native 5G Deployments. In Proceedings of the IEEE/IFIP Network Operations and Management Symposium (NOMS). Miami, Florida, USA, 08 - 12 May, 2023. 73 | 74 | 75 | # Contributions 76 | Contributions, improvements to documentation, and bug-fixes are always welcome! 77 | See [first-contributions](https://github.com/firstcontributions/first-contributions). 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /adaptive_monitoring/README.md: -------------------------------------------------------------------------------- 1 | # adaptive-polling 2 | This folder contains an adaptive polling heuristic designed for use with the Monarch network slice monitoring architecture. The adaptive polling technique dynamically adjusts the polling intervals based on network conditions, aiming to optimize the monitoring process in 5G networks. 3 | 4 | ## dataset 5 | The dataset located in the [data](data) folder includes KPIs collected using Monarch by replaying a [real-world 5G dataset](https://ieee-dataport.org/documents/5g-traffic-datasets) through our testbed. This dataset was used to test and validate the adaptive polling heuristic. -------------------------------------------------------------------------------- /adaptive_monitoring/collect_dataset.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import pandas as pd 3 | import logging 4 | from datetime import datetime, timedelta 5 | import matplotlib.pyplot as plt 6 | import matplotlib.dates as mdates 7 | import pytz 8 | from src.config import QUERIES, SCENARIOS 9 | from dotenv import load_dotenv 10 | load_dotenv() 11 | import os 12 | 13 | # Setup logger for console output 14 | logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] [%(filename)s] %(message)s') 15 | logger = logging.getLogger(__name__) 16 | 17 | class PrometheusQuerier: 18 | def __init__(self, prometheus_url, local_timezone='America/Toronto'): 19 | self.prometheus_url = prometheus_url 20 | self.local_timezone = local_timezone 21 | self.query_endpoint = f"{prometheus_url}/api/v1/query_range" 22 | 23 | def get_time_range(self, start_time_str=None, end_time_str=None): 24 | if start_time_str and end_time_str: 25 | start_time = self.convert_local_to_utc(start_time_str) 26 | end_time = self.convert_local_to_utc(end_time_str) 27 | else: 28 | utc_now = datetime.utcnow() 29 | end_time = utc_now.strftime('%Y-%m-%dT%H:%M:%SZ') 30 | start_time = (utc_now - timedelta(minutes=5)).strftime('%Y-%m-%dT%H:%M:%SZ') 31 | return start_time, end_time 32 | 33 | @staticmethod 34 | def convert_local_to_utc(local_time_str, local_timezone='America/Toronto', output_format='%Y-%m-%dT%H:%M:%SZ'): 35 | local_time = datetime.strptime(local_time_str, '%Y-%m-%d %H:%M:%S') 36 | local_timezone_obj = pytz.timezone(local_timezone) 37 | local_time = local_timezone_obj.localize(local_time) 38 | utc_time = local_time.astimezone(pytz.utc) 39 | return utc_time.strftime(output_format) 40 | 41 | def query_prometheus(self, query_string, start_time_str=None, end_time_str=None, chunk_size_hours=1): 42 | # Initialize result dataframe 43 | df_result = pd.DataFrame(columns=["timestamp", "value"]) 44 | 45 | start_time, end_time = self.get_time_range(start_time_str, end_time_str) 46 | start_time = datetime.strptime(start_time, '%Y-%m-%dT%H:%M:%SZ') 47 | end_time = datetime.strptime(end_time, '%Y-%m-%dT%H:%M:%SZ') 48 | chunk_size_seconds = chunk_size_hours * 3600 49 | 50 | while start_time < end_time: 51 | chunk_end_time = min(start_time + timedelta(seconds=chunk_size_seconds), end_time) 52 | data = { 53 | 'query': query_string, 54 | 'start': start_time.strftime('%Y-%m-%dT%H:%M:%SZ'), 55 | 'end': chunk_end_time.strftime('%Y-%m-%dT%H:%M:%SZ'), 56 | 'step': '1s' 57 | } 58 | response = requests.post(url=self.query_endpoint, data=data, verify=False).json() 59 | df_chunk = self.extract_values(response) 60 | df_result = pd.concat([df_result, df_chunk], ignore_index=True) 61 | start_time += timedelta(seconds=chunk_size_seconds) 62 | 63 | df_result["value"] = pd.to_numeric(df_result["value"]) 64 | return df_result 65 | 66 | @staticmethod 67 | def extract_values(prometheus_response): 68 | if prometheus_response["status"] == "error": 69 | logger.error("Error in Prometheus response!") 70 | return pd.DataFrame(columns=["timestamp", "value"]) 71 | else: 72 | if not prometheus_response["data"]["result"]: 73 | logger.warning("Empty result in Prometheus response!") 74 | return pd.DataFrame(columns=["timestamp", "value"]) 75 | result = prometheus_response["data"]["result"][0]["values"] 76 | return pd.DataFrame(result, columns=["timestamp", "value"]) 77 | 78 | def main(): 79 | prometheus_url = os.getenv("MONARCH_PROMETHEUS_URL") 80 | querier = PrometheusQuerier(prometheus_url) 81 | 82 | queries = QUERIES # This is a dictionary of queries to be executed 83 | scenario = SCENARIOS["test_with_time"] 84 | scenario_name = scenario["name"] 85 | start_time = scenario.get("start_time") 86 | end_time = scenario.get("end_time") 87 | kpis = scenario.get("kpis") 88 | queries = {k: v for k, v in queries.items() if k in kpis} 89 | logger.info(f"Scenario: {scenario_name}") 90 | 91 | os.makedirs(f"data/{scenario_name}", exist_ok=True) 92 | for name, query in queries.items(): 93 | logger.info(f"Querying {name}") 94 | df = querier.query_prometheus(query, start_time, end_time) 95 | df.to_csv(f"data/{scenario_name}/{name}.csv", index=False) 96 | 97 | logger.info(f"Done! Stored data in data/{scenario_name}") 98 | 99 | if __name__ == "__main__": 100 | main() 101 | -------------------------------------------------------------------------------- /adaptive_monitoring/data/README.md: -------------------------------------------------------------------------------- 1 | # data 2 | KPIs collected using Monarch by replaying a [real-world 5G dataset](https://ieee-dataport.org/documents/5g-traffic-datasets) through our testbed. -------------------------------------------------------------------------------- /adaptive_monitoring/requirements.txt: -------------------------------------------------------------------------------- 1 | statsmodels 2 | scikit-learn 3 | seaborn -------------------------------------------------------------------------------- /adaptive_monitoring/settings.yaml: -------------------------------------------------------------------------------- 1 | kpi: 2 | slice_throughput: 3 | enabled: true 4 | unit: Throughput (Mbps) 5 | scale_factor: 1000000 6 | path: data/cloud_gaming/slice_throughput.csv 7 | upf_cpu_usage: 8 | enabled: false 9 | unit: CPU Usage 10 | scale_factor: 1 11 | path: data/cloud_gaming/upf_cpu_usage.csv 12 | upf_mem_usage: 13 | enabled: false 14 | unit: Memory Usage (MB) 15 | scale_factor: 1000000 16 | path: data/cloud_gaming/upf_mem_usage.csv 17 | upf_energy_usage: 18 | enabled: false 19 | unit: Energy Usage (J) 20 | scale_factor: 1 21 | path: data/cloud_gaming/upf_energy_usage.csv 22 | 23 | schemes: 24 | ff5: true 25 | ff10: true 26 | adaptive: true 27 | 28 | plots: 29 | timeseries: true 30 | distribution: false 31 | error_timeseries: false 32 | error_timeseries_smooth: false 33 | error_distribution: false 34 | psd: false 35 | -------------------------------------------------------------------------------- /adaptive_monitoring/src/config.py: -------------------------------------------------------------------------------- 1 | QUERIES = { 2 | # Query to calculate the throughput by slicing data volume on N3 interface 3 | # for a given Session Establishment ID (SEID), accounting for the session's S-NSSAI. 4 | "slice_throughput": ''' 5 | sum by (seid) ( 6 | rate(fivegs_ep_n3_gtp_indatavolumen3upf_seid[1m]) 7 | * on (seid) group_right 8 | sum(fivegs_smffunction_sm_seid_session{snssai="1-000001"}) by (seid, snssai) 9 | ) * 8 10 | ''', 11 | 12 | # Query to calculate the CPU usage of the UPF component, 13 | # normalized by the CPU resources requested for the UPF container. 14 | "upf_cpu_usage": ''' 15 | rate(container_cpu_usage_seconds_total{ 16 | namespace="open5gs", 17 | container="upf", 18 | pod=~".*upf1.*" 19 | }[1m]) 20 | / on() group_left() 21 | kube_pod_container_resource_requests{ 22 | resource="cpu", 23 | namespace="open5gs", 24 | container="upf", 25 | pod=~".*upf1.*" 26 | } 27 | ''', 28 | 29 | # Query to fetch the current memory usage of the UPF component. 30 | "upf_memory_usage": ''' 31 | container_memory_working_set_bytes{ 32 | namespace="open5gs", 33 | pod="open5gs-upf1-555878cc65-bpf8f", 34 | job="kubelet", 35 | container="upf" 36 | } 37 | ''', 38 | 39 | # Query to calculate the energy consumption of the UPF component. 40 | "upf_energy_consumption": ''' 41 | sum by (pod_name) ( 42 | rate(kepler_container_joules_total{pod_name=~".*upf1.*"}[1m]) 43 | ) 44 | ''', 45 | 46 | # Query to calculate the CPU usage of the SMF component, 47 | # normalized by the CPU resources requested for the SMF container. 48 | "smf_cpu_usage": ''' 49 | rate(container_cpu_usage_seconds_total{ 50 | namespace="open5gs", 51 | container="smf", 52 | pod=~".*smf1.*" 53 | }[1m]) 54 | / on() group_left() 55 | kube_pod_container_resource_requests{ 56 | resource="cpu", 57 | namespace="open5gs", 58 | container="smf", 59 | pod=~".*smf1.*" 60 | } 61 | ''', 62 | 63 | # Query to fetch the duration of the scraping process for the UPF component metrics. 64 | "upf_scrape_duration": ''' 65 | scrape_duration_seconds{ 66 | namespace="open5gs", 67 | pod="open5gs-upf1-555878cc65-bpf8f" 68 | } 69 | ''', 70 | "temperature": '''temperature_mde_celsius{mde="mde-1s"}''' 71 | } 72 | 73 | SCENARIOS = { 74 | "cloud_gaming": { 75 | "name": "cloud_gaming", 76 | "description": "Cloud gaming dataset replayed through the 5G testbed.", 77 | "kpis": ["slice_throughput"], 78 | "start_time": "2023-12-06 13:00:00", 79 | "end_time": "2023-12-06 16:30:00" 80 | }, 81 | "test": { 82 | "name": "test", 83 | "description": "Test scenario.", 84 | "kpis": ["temperature"] 85 | }, 86 | "test_with_time": { 87 | "name": "test_with_time", 88 | "description": "Test scenario with start and end time.", 89 | "kpis": ["temperature", "upf_cpu_usage"], 90 | "start_time": "2024-02-17 15:00:00", 91 | "end_time": "2024-02-17 15:20:00" 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /adaptive_monitoring/src/sampling.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from abc import ABC, abstractmethod 3 | import pandas as pd 4 | import numpy as np 5 | 6 | class BaseSampler(ABC): 7 | def __init__(self): 8 | self.sampled_df = pd.DataFrame(columns=['value']) 9 | 10 | @abstractmethod 11 | def sample(self, timestamp, value): 12 | pass 13 | 14 | def get_sampled_df(self): 15 | return self.sampled_df 16 | 17 | 18 | class FixedFrequencySampler(BaseSampler): 19 | def __init__(self, frequency): 20 | super().__init__() 21 | self.frequency = frequency 22 | self.last_sampled_timestamp = None 23 | 24 | def sample(self, timestamp, value): 25 | if self.last_sampled_timestamp is None \ 26 | or (timestamp - self.last_sampled_timestamp).total_seconds() >= self.frequency: 27 | self.sampled_df.loc[timestamp] = {'value': value} # Set 'timestamp' as the index 28 | self.last_sampled_timestamp = timestamp 29 | 30 | 31 | class AdaptiveSampler(BaseSampler): 32 | def __init__(self, threshold: float = 0.01) -> None: 33 | super().__init__() 34 | self.threshold = threshold 35 | self.last_sampled_timestamp = None 36 | self.last_sampled_value = None 37 | self.min_interval = 3 38 | self.max_interval = 10 39 | self.sampling_interval = self.min_interval 40 | self.max_value = -np.inf 41 | self.min_value = np.inf 42 | self.increase_factor = 1.5 43 | self.decrease_factor = 0.5 44 | 45 | def sample_datapoint(self, timestamp, value): 46 | 47 | # sample the data point 48 | self.sampled_df.loc[timestamp] = {'value': value} 49 | self.last_sampled_timestamp = timestamp 50 | self.last_sampled_value = value 51 | 52 | # update the min and max values 53 | self.max_value = max(self.max_value, value) 54 | self.min_value = min(self.min_value, value) 55 | 56 | 57 | def sample(self, timestamp, value): 58 | 59 | print(f"Sampling interval: {self.sampling_interval}") 60 | 61 | if self.last_sampled_timestamp is None: 62 | self.sample_datapoint(timestamp, value) 63 | return 64 | 65 | self.update_sampling_interval(value) 66 | if self.is_time_to_sample(timestamp): 67 | self.sample_datapoint(timestamp, value) 68 | 69 | def is_time_to_sample(self, timestamp): 70 | elapsed_time = (timestamp - self.last_sampled_timestamp).total_seconds() 71 | return elapsed_time >= self.sampling_interval 72 | 73 | def is_significant_change(self, value): 74 | change = abs(value - self.last_sampled_value) 75 | relative_change = change / value 76 | # print(f"Relative Change: {relative_change:.6f}, Threshold: {self.threshold:.6f}") 77 | return relative_change > self.threshold 78 | 79 | def update_sampling_interval(self, value): 80 | if self.is_significant_change(value): 81 | # self.sampling_interval = max(self.min_interval, self.sampling_interval - 1) 82 | self.sampling_interval = max(self.min_interval, int(self.sampling_interval * self.decrease_factor)) 83 | else: 84 | # self.sampling_interval = min(self.max_interval, self.sampling_interval + 2) 85 | self.sampling_interval = min(self.max_interval, int(self.sampling_interval * self.increase_factor)) 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /adaptive_monitoring/src/utils.py: -------------------------------------------------------------------------------- 1 | from sklearn.metrics import mean_squared_error 2 | import pandas as pd 3 | import numpy as np 4 | 5 | 6 | def upscale_df(sampled_df, original_df): 7 | """Upscale the sampled DataFrame to match the original DataFrame.""" 8 | return sampled_df.resample('1s').interpolate(method='linear').reindex(original_df.index).ffill() 9 | 10 | 11 | def truncate_df(df, date, N): 12 | """Truncate the DataFrame to a smaller size of N rows starting from the given date.""" 13 | return df[df.index >= date].head(N) 14 | 15 | 16 | def mean_absolute_error(df, sampled_df, scale_factor=1): 17 | """Calculate the absolute error between two DataFrames.""" 18 | # upscale the sampled dataframe to match the original dataframe 19 | sampled_df = upscale_df(sampled_df, df) 20 | 21 | if df.shape != sampled_df.shape: 22 | raise ValueError("DataFrames must have the same shape.") 23 | 24 | values = df["value"].to_numpy() / scale_factor 25 | sampled_values = sampled_df["value"].to_numpy() / scale_factor 26 | 27 | return np.abs(values - sampled_values).mean() 28 | 29 | def mean_absolute_percentage_error(df, sampled_df, scale_factor=1, epsilon=1e-8): 30 | """Calculate the absolute percentage error between two DataFrames.""" 31 | # upscale the sampled dataframe to match the original dataframe 32 | sampled_df = upscale_df(sampled_df, df) 33 | 34 | if df.shape != sampled_df.shape: 35 | raise ValueError("DataFrames must have the same shape.") 36 | 37 | values = df["value"].to_numpy() / scale_factor 38 | sampled_values = sampled_df["value"].to_numpy() / scale_factor 39 | 40 | absolute_percentage_errrors = np.abs((values - sampled_values) / (values + epsilon)) * 100 41 | mape = absolute_percentage_errrors.mean() 42 | return mape 43 | 44 | 45 | def pointwise_absolute_error(df, sampled_df, scale_factor=1): 46 | """Calculate pointwise error two DataFrames.""" 47 | 48 | # upscale the sampled dataframe to match the original dataframe 49 | sampled_df = upscale_df(sampled_df, df) 50 | 51 | if df.shape != sampled_df.shape: 52 | raise ValueError("DataFrames must have the same shape.") 53 | 54 | values = df["value"].to_numpy() / scale_factor 55 | sampled_values = sampled_df["value"].to_numpy() / scale_factor 56 | 57 | return np.abs(values - sampled_values) 58 | 59 | 60 | def compression_ratio(original_df, sampled_df): 61 | """Calculate the reduction in data points.""" 62 | original_rows = original_df.shape[0] 63 | sampled_rows = sampled_df.shape[0] 64 | if original_rows == 0: 65 | return 0 66 | compression_ratio = 1 - (sampled_rows / original_rows) 67 | return compression_ratio 68 | 69 | def preprocess_data(file_path, normalize=False): 70 | """Preprocess the dataset for analysis.""" 71 | df = pd.read_csv(file_path) 72 | df['date'] = pd.to_datetime(df['timestamp'], unit='s') 73 | df.set_index('date', inplace=True) 74 | df.drop(["timestamp"], inplace=True, axis=1) 75 | df = df[~df.index.duplicated(keep='first')] 76 | if normalize: 77 | df = normalize(df) 78 | return df 79 | 80 | def normalize(df): 81 | """Normalize the dataset using min-max scaling.""" 82 | return (df - df.min()) / (df.max() - df.min()) -------------------------------------------------------------------------------- /bin/check-inotify.py: -------------------------------------------------------------------------------- 1 | # System defaults for comparison 2 | default_max_user_instances = 128 3 | default_max_queued_events = 16384 4 | default_max_user_watches = 8192 5 | 6 | # Total available memory in KB for the inotify settings 7 | available_memory_kb = 2 * 1024 * 1024 # 2 GB in KB 8 | 9 | # Calculate the total "weight" based on default values to keep the same ratio 10 | total_weight = default_max_user_watches + default_max_user_watches + default_max_user_watches 11 | 12 | # Calculate how much memory each "unit" represents 13 | memory_per_unit = available_memory_kb / total_weight 14 | 15 | # Allocate memory based on the original ratio 16 | print("fs.inotify.max_user_watches =", int(memory_per_unit * default_max_user_watches)) 17 | print("fs.inotify.max_user_instances =", int(memory_per_unit * default_max_user_instances)) 18 | print("fs.inotify.max_queued_events =", int(memory_per_unit * default_max_queued_events)) -------------------------------------------------------------------------------- /bin/clear_cni.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # https://docs.microfocus.com/doc/OMT/2022.11/FailToCreatePodSandBox 3 | systemctl stop kubelet 4 | systemctl stop containerd 5 | ls /var/lib/cni/networks/ 6 | mv /var/lib/cni/networks /var/lib/cni/networks.bak 7 | mkdir /var/lib/cni/networks 8 | systemctl start containerd 9 | systemctl start kubelet -------------------------------------------------------------------------------- /bin/inotify-consumers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the procs sorted by the number of inotify watches 4 | # @author Carl-Erik Kopseng 5 | # @latest https://github.com/fatso83/dotfiles/blob/master/utils/scripts/inotify-consumers 6 | # Discussion leading up to answer: https://unix.stackexchange.com/questions/15509/whos-consuming-my-inotify-resources 7 | # 8 | # 9 | ########################################## Notice ########################################## 10 | ### Since Fall 2022 you should prefer using the following C++ version ### 11 | ### https://github.com/mikesart/inotify-info ### 12 | ############################################################################################ 13 | # 14 | # 15 | # The fastest version of this script is here: https://github.com/fatso83/dotfiles/commit/inotify-consumers-v1-fastest 16 | # Later PRs introduced significant slowdowns at the cost of better output, but it is insignificant on most machines 17 | # See this for details: https://github.com/fatso83/dotfiles/pull/10#issuecomment-1122374716 18 | 19 | main(){ 20 | printf "\n%${WLEN}s %${WLEN}s\n" "INOTIFY" "INSTANCES" 21 | printf "%${WLEN}s %${WLEN}s\n" "WATCHES" "PER " 22 | printf "%${WLEN}s %${WLEN}s %s\n" " COUNT " "PROCESS " "PID USER COMMAND" 23 | printf -- "------------------------------------------------------------\n" 24 | generateData 25 | } 26 | 27 | usage(){ 28 | cat << EOF 29 | Usage: $0 [--help|--limits] 30 | -l, --limits Will print the current related limits and how to change them 31 | -h, --help Show this help 32 | 33 | FYI: Check out Michael Sartain's C++ take on this script. The resulting native executable 34 | is much faster, modern and feature rich. It can be found at 35 | https://github.com/mikesart/inotify-info 36 | 37 | Requires building, but is well worth the few seconds :) 38 | EOF 39 | } 40 | 41 | limits(){ 42 | printf "\nCurrent limits\n-------------\n" 43 | sysctl fs.inotify.max_user_instances fs.inotify.max_user_watches 44 | 45 | cat <<- EOF 46 | Changing settings permanently 47 | ----------------------------- 48 | echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf 49 | sudo sysctl -p # re-read config 50 | EOF 51 | } 52 | 53 | generateData() { 54 | local -i PROC 55 | local -i PID 56 | local -i CNT 57 | local -i INSTANCES 58 | local -i TOT 59 | local -i TOTINSTANCES 60 | # read process list into cache 61 | local PSLIST="$(ps ax -o pid,user=WIDE-COLUMN,command $COLSTRING)" 62 | local INOTIFY="$(find /proc/[0-9]*/fdinfo -type f 2>/dev/null | xargs grep ^inotify 2>/dev/null)" 63 | local INOTIFYCNT="$(echo "$INOTIFY" | cut -d "/" -s --output-delimiter=" " -f 3 |uniq -c | sed -e 's/:.*//')" 64 | # unique instances per process is denoted by number of inotify FDs 65 | local INOTIFYINSTANCES="$(echo "$INOTIFY" | cut -d "/" -s --output-delimiter=" " -f 3,5 | sed -e 's/:.*//'| uniq |awk '{print $1}' |uniq -c)" 66 | local INOTIFYUSERINSTANCES="$(echo "$INOTIFY" | cut -d "/" -s --output-delimiter=" " -f 3,5 | sed -e 's/:.*//' | uniq | 67 | while read PID FD; do echo $PID $FD $(grep -e "^ *${PID} " <<< "$PSLIST"|awk '{print $2}'); done | cut -d" " -f 3 | sort | uniq -c |sort -nr)" 68 | set -e 69 | 70 | cat <<< "$INOTIFYCNT" | 71 | { 72 | while read -rs CNT PROC; do # count watches of processes found 73 | echo "${PROC},${CNT},$(echo "$INOTIFYINSTANCES" | grep " ${PROC}$" |awk '{print $1}')" 74 | done 75 | } | 76 | grep -v ",0," | # remove entires without watches 77 | sort -n -t "," -k 2,3 -r | # sort to begin with highest numbers 78 | { # group commands so that $TOT is visible in the printf 79 | IFS="," 80 | while read -rs PID CNT INSTANCES; do # show watches and corresponding process info 81 | printf "%$(( WLEN - 2 ))d %$(( WLEN - 2 ))d %s\n" "$CNT" "$INSTANCES" "$(grep -e "^ *${PID} " <<< "$PSLIST")" 82 | TOT=$(( TOT + CNT )) 83 | TOTINSTANCES=$(( TOTINSTANCES + INSTANCES)) 84 | done 85 | # These stats should be per-user as well, since inotify limits are per-user.. 86 | printf "\n%$(( WLEN - 2 ))d %s\n" "$TOT" "WATCHES TOTAL COUNT" 87 | # the total across different users is somewhat meaningless, not printing for now. 88 | # printf "\n%$(( WLEN - 2 ))d %s\n" "$TOTINSTANCES" "TOTAL INSTANCES COUNT" 89 | } 90 | echo "" 91 | echo "INotify instances per user (e.g. limits specified by fs.inotify.max_user_instances): " 92 | echo "" 93 | ( 94 | echo "INSTANCES USER" 95 | echo "----------- ------------------" 96 | echo "$INOTIFYUSERINSTANCES" 97 | ) | column -t 98 | echo "" 99 | exit 0 100 | } 101 | 102 | # get terminal width 103 | declare -i COLS=$(tput cols 2>/dev/null || echo 80) 104 | declare -i WLEN=10 105 | declare COLSTRING="--columns $(( COLS - WLEN ))" # get terminal width 106 | 107 | if [ "$1" = "--limits" -o "$1" = "-l" ]; then 108 | limits 109 | exit 0 110 | fi 111 | 112 | if [ "$1" = "--help" -o "$1" = "-h" ]; then 113 | usage 114 | exit 0 115 | fi 116 | 117 | # added this line and moved some declarations to allow for the full display instead of a truncated version 118 | if [ "$1" = "--full" -o "$1" = "-f" ]; then 119 | unset COLSTRING 120 | main 121 | fi 122 | 123 | if [ -n "$1" ]; then 124 | printf "\nUnknown parameter '$1'\n" >&2 125 | usage 126 | exit 1 127 | fi 128 | main -------------------------------------------------------------------------------- /bin/k8s-log.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ $# -eq 0 ]]; then 4 | echo "Usage: k8s-logs [namespace]" 5 | exit 1 6 | fi 7 | 8 | keyword=$1 9 | namespace=$2 10 | 11 | if [[ -z $namespace ]]; then 12 | echo "Select namespace:" 13 | namespaces=($(kubectl get namespaces -o name | cut -d/ -f2)) 14 | PS3="Namespace: " 15 | select namespace in "${namespaces[@]}"; do 16 | if [[ -n $namespace ]]; then 17 | break 18 | fi 19 | done 20 | fi 21 | 22 | pod=$(kubectl get pods -n "$namespace" | grep "$keyword" | head -1 | awk '{print $1}') 23 | if [[ -z $pod ]]; then 24 | echo "No pods found for keyword: $keyword" 25 | exit 1 26 | fi 27 | 28 | containers=($(kubectl get pods "$pod" -n "$namespace" -o jsonpath='{.spec.containers[*].name}')) 29 | if [[ ${#containers[@]} -eq 1 ]]; then 30 | container=${containers[0]} 31 | else 32 | echo "Select container:" 33 | PS3="Container: " 34 | select container in "${containers[@]}"; do 35 | if [[ -n $container ]]; then 36 | break 37 | fi 38 | done 39 | fi 40 | 41 | kubectl logs -f "$pod" -n "$namespace" -c "$container" 42 | 43 | -------------------------------------------------------------------------------- /bin/k8s-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Monitor overall Kubernetes cluster utilization and capacity. 4 | # 5 | # Original source: 6 | # https://github.com/kubernetes/kubernetes/issues/17512#issuecomment-367212930 7 | # 8 | # Tested with: 9 | # - AWS EKS v1.11.5 10 | # 11 | # Does not require any other dependencies to be installed in the cluster. 12 | # Source: https://www.jeffgeerling.com/blog/2019/monitoring-kubernetes-cluster-utilization-and-capacity-poor-mans-way 13 | 14 | set -e 15 | 16 | KUBECTL="kubectl" 17 | NODES=$($KUBECTL get nodes --no-headers -o custom-columns=NAME:.metadata.name) 18 | 19 | function usage() { 20 | local node_count=0 21 | local total_percent_cpu=0 22 | local total_percent_mem=0 23 | local readonly nodes=$@ 24 | 25 | for n in $nodes; do 26 | local requests=$($KUBECTL describe node $n | grep -A3 -E "\\s\sRequests" | tail -n2) 27 | local percent_cpu=$(echo $requests | awk -F "[()%]" '{print $2}') 28 | local percent_mem=$(echo $requests | awk -F "[()%]" '{print $8}') 29 | echo "$n: ${percent_cpu}% CPU, ${percent_mem}% memory" 30 | 31 | node_count=$((node_count + 1)) 32 | total_percent_cpu=$((total_percent_cpu + percent_cpu)) 33 | total_percent_mem=$((total_percent_mem + percent_mem)) 34 | done 35 | 36 | local readonly avg_percent_cpu=$((total_percent_cpu / node_count)) 37 | local readonly avg_percent_mem=$((total_percent_mem / node_count)) 38 | 39 | echo "Average usage: ${avg_percent_cpu}% CPU, ${avg_percent_mem}% memory." 40 | } 41 | 42 | usage $NODES 43 | -------------------------------------------------------------------------------- /bin/k8s-shell.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # function to print usage message 4 | function usage { 5 | echo "Usage: $0 keyword [namespace]" 6 | echo " keyword: a string that identifies the pod you want to access, e.g. amf, smf, upf" 7 | echo " namespace (optional): the Kubernetes namespace where the pod is running" 8 | echo "If no namespace is specified, a namespace picker will be displayed." 9 | } 10 | 11 | # Function to prompt the user to select a namespace from a list 12 | select_namespace() { 13 | echo "Select a namespace:" 14 | select NAMESPACE in $(kubectl get namespaces -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}') 15 | do 16 | if [ -n "$NAMESPACE" ] 17 | then 18 | break 19 | fi 20 | done 21 | } 22 | 23 | # Function to prompt the user to select a container from a list 24 | select_container() { 25 | echo "Select a container:" 26 | select CONTAINER in $(kubectl get pod $POD -n $NAMESPACE -o jsonpath='{range .spec.containers[*]}{.name}{"\n"}{end}') 27 | do 28 | if [ -n "$CONTAINER" ] 29 | then 30 | break 31 | fi 32 | done 33 | } 34 | 35 | # Display help message if no arguments provided 36 | if [ "$#" -eq 0 ] 37 | then 38 | usage 39 | exit 1 40 | fi 41 | 42 | POD_KEYWORD=$1 43 | 44 | # Prompt for namespace if not specified 45 | if [ "$#" -eq 1 ] 46 | then 47 | select_namespace 48 | else 49 | NAMESPACE=$2 50 | fi 51 | 52 | POD=$(kubectl get pods -n $NAMESPACE | grep "$POD_KEYWORD" | awk '{print $1}') 53 | 54 | if [ -z "$POD" ] 55 | then 56 | echo "No pod found with keyword: $POD_KEYWORD in namespace: $NAMESPACE" 57 | exit 1 58 | fi 59 | 60 | CONTAINER_COUNT=$(kubectl get pod $POD -n $NAMESPACE -o jsonpath='{range .spec.containers[*]}{.name}{"\n"}{end}' | wc -l) 61 | 62 | if [ "$CONTAINER_COUNT" -eq 1 ] 63 | then 64 | CONTAINER=$(kubectl get pod $POD -n $NAMESPACE -o jsonpath='{.spec.containers[0].name}') 65 | else 66 | select_container 67 | fi 68 | 69 | # Try bash shell, fallback to sh 70 | SHELLS=("bash" "sh") 71 | for SHELL in "${SHELLS[@]}" 72 | do 73 | if kubectl exec $POD -n $NAMESPACE -c $CONTAINER -- $SHELL -c 'exit 0' > /dev/null 2>&1 74 | then 75 | kubectl exec -it $POD -n $NAMESPACE -c $CONTAINER -- $SHELL 76 | exit 0 77 | fi 78 | done 79 | 80 | echo "No supported shell found in pod: $POD" 81 | exit 1 82 | 83 | -------------------------------------------------------------------------------- /bin/k8s-ue-pinger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # function to print usage message 4 | function usage { 5 | echo "Usage: $0 [namespace]" 6 | echo " namespace (optional): the Kubernetes namespace where the pods are running" 7 | echo "This script will ping google.com from all pods containing 'ue' in their names." 8 | } 9 | 10 | # Function to prompt the user to select a namespace from a list 11 | select_namespace() { 12 | echo "Select a namespace:" 13 | select NAMESPACE in $(kubectl get namespaces -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}') 14 | do 15 | if [ -n "$NAMESPACE" ] 16 | then 17 | break 18 | fi 19 | done 20 | } 21 | 22 | # Display help message if no namespace is provided 23 | if [ "$#" -eq 0 ] 24 | then 25 | select_namespace 26 | else 27 | NAMESPACE=$1 28 | fi 29 | 30 | PODS=$(kubectl get pods -n $NAMESPACE | grep "ueransim-ue" | awk '{print $1}') 31 | 32 | if [ -z "$PODS" ] 33 | then 34 | echo "No pods found containing 'ueransim-ue' in namespace: $NAMESPACE" 35 | exit 1 36 | fi 37 | 38 | echo "Initiating ping to google.com from UE pods..." 39 | 40 | # Loop through all matching pods and execute ping command, logging output 41 | for POD in $PODS 42 | do 43 | CONTAINER=$(kubectl get pod $POD -n $NAMESPACE -o jsonpath='{.spec.containers[0].name}') 44 | echo "Pinging from pod: $POD, container: $CONTAINER" 45 | # Creating a unique log file for each pod to avoid conflicts 46 | LOGFILE="ping-$(date +%s)-$POD.log" 47 | kubectl exec $POD -n $NAMESPACE -c $CONTAINER -- sh -c "ping -I uesimtun0 google.com > /tmp/$LOGFILE 2>&1 &" 48 | echo "Ping initiated in pod: $POD, container: $CONTAINER. Logs at /tmp/$LOGFILE" 49 | done 50 | -------------------------------------------------------------------------------- /bin/print-nodeports.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | kubectl get svc --all-namespaces -o go-template='{{range .items}}{{range.spec.ports}}{{if .nodePort}}{{.nodePort}}{{"\n"}}{{end}}{{end}}{{end}}' 3 | 4 | -------------------------------------------------------------------------------- /data_distribution/README.md: -------------------------------------------------------------------------------- 1 | # thanos 2 | Thanos deployment 3 | 4 | # install 5 | Use the [bitnami/thanos helm chart](https://artifacthub.io/packages/helm/bitnami/thanos) 6 | 7 | ``` 8 | helm install thanos -n thanos -f thanos-values.yaml bitnami/thanos 9 | ``` 10 | 11 | # enabling thanos components in prometheus helm chart 12 | 13 | Remember to create the `thanos-objstore-config` secret first. 14 | 15 | ```yaml 16 | thanos: 17 | image: quay.io/thanos/thanos:v0.31.0 18 | objectStorageConfig: 19 | key: thanos.yaml 20 | name: thanos-objstore-config 21 | 22 | prometheus: 23 | thanosServiceExternal: 24 | enabled: true 25 | type: NodePort 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /data_distribution/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | THANOS_VER="12.4.3" 3 | HELM_REPO_URL="https://charts.bitnami.com/bitnami" 4 | HELM_REPO_NAME="bitnami" 5 | NAMESPACE="monarch" 6 | MODULE_NAME="datadist" 7 | 8 | set -o allexport; source ../.env; set +o allexport 9 | 10 | kubectl get namespace $NAMESPACE 2>/dev/null || kubectl create namespace $NAMESPACE 11 | helm repo add $HELM_REPO_NAME $HELM_REPO_URL || echo "Helm repo $HELM_REPO_NAME already exists." 12 | helm repo update 13 | 14 | 15 | envsubst < thanos-values.yaml | helm upgrade --install $MODULE_NAME $HELM_REPO_NAME/thanos \ 16 | --namespace $NAMESPACE \ 17 | --version $THANOS_VER \ 18 | -f - -------------------------------------------------------------------------------- /data_distribution/test-data-distribution.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | print_subheader() { 4 | echo -e "\e[1;36m--- $1 ---\e[0m" 5 | } 6 | 7 | print_header() { 8 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 9 | } 10 | 11 | print_success() { 12 | echo -e "\e[1;32m$1\e[0m" 13 | } 14 | 15 | print_error() { 16 | echo -e "\e[1;31mERROR: $1\e[0m" 17 | } 18 | 19 | print_info() { 20 | echo -e "\e[1;33mINFO: $1\e[0m" 21 | } 22 | 23 | # Function to send GET request and handle response 24 | get_stores_data() { 25 | local url=$1 26 | response=$(curl -s -X GET "$url") 27 | 28 | # Check if the curl request was successful 29 | if [ $? -ne 0 ]; then 30 | print_error "Failed to fetch data from $url" 31 | exit 1 32 | fi 33 | 34 | echo "$response" 35 | } 36 | print_header "Testing Data Distribution (Monarch Core [2/5])" 37 | print_subheader "Performing health check for Data Distribution" 38 | response=$(curl -s -w "%{http_code}" -o /dev/null http://localhost:31004/-/healthy) 39 | 40 | if [ "$response" -eq 200 ]; then 41 | print_success "Health check passed! Data Distribution is healthy." 42 | else 43 | print_error "Health check failed with status code: $response. Please check the Data Distribution logs for details." 44 | fi 45 | 46 | 47 | print_subheader "Fetching stores data from API" 48 | response=$(get_stores_data "http://localhost:31004/api/v1/stores") 49 | 50 | # Check if the response is empty 51 | if [ -z "$response" ]; then 52 | print_error "Received empty response from the API. Exiting..." 53 | exit 1 54 | fi 55 | 56 | # Check if "sidecar" exists and print its info if found 57 | sidecar_exists=$(echo "$response" | jq -r '.data.sidecar[]') 58 | 59 | if [ -z "$sidecar_exists" ]; then 60 | print_info "Sidecar(s) should be discovered after deploying NSSDC." 61 | else 62 | print_success "NSSDC Sidecar(s) found:" 63 | echo "$sidecar_exists" | jq -r '"Endpoint: \(.name), Status: \(.lastError // "null" | if . == "null" then "UP" else "DOWN" end), Cluster: \(.labelSets[0].cluster // "N/A")"' 64 | fi 65 | -------------------------------------------------------------------------------- /data_distribution/thanos-values.yaml: -------------------------------------------------------------------------------- 1 | objstoreConfig: |- 2 | type: S3 3 | config: 4 | bucket: monarch-thanos 5 | endpoint: "${MONARCH_MINIO_ENDPOINT}" 6 | access_key: ${MONARCH_MINIO_ACCESS_KEY} 7 | secret_key: ${MONARCH_MINIO_SECRET_KEY} 8 | insecure: true 9 | 10 | query: 11 | enabled: true 12 | dnsDiscovery: 13 | enabled: true 14 | sidecarsService: prometheus-kube-prometheus-thanos-discovery 15 | sidecarsNamespace: prometheus 16 | stores: 17 | - "${MONARCH_THANOS_STORE_GRPC}" # grpc 18 | - "${MONARCH_THANOS_STORE_HTTP}" # http 19 | 20 | queryFrontend: 21 | enabled: true 22 | service: 23 | type: NodePort 24 | ports: 25 | http: 9065 26 | nodePorts: 27 | http: 31004 28 | resources: 29 | requests: 30 | memory: "512Mi" 31 | cpu: "200m" 32 | 33 | bucketweb: 34 | enabled: false 35 | 36 | compactor: 37 | enabled: false 38 | 39 | storegateway: 40 | enabled: true 41 | 42 | ruler: 43 | enabled: false 44 | 45 | receive: 46 | enabled: true 47 | service: 48 | type: NodePort 49 | nodePorts: 50 | http: 31005 51 | grpc: 31006 52 | remote: 31007 53 | 54 | metrics: 55 | enabled: false 56 | serviceMonitor: 57 | enabled: true 58 | namespace: monarch 59 | interval: 1s 60 | -------------------------------------------------------------------------------- /data_distribution/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | KUBE_PROMETHEUS_STACK_VER="51.9.4" 3 | HELM_REPO_URL="https://prometheus-community.github.io/helm-charts" 4 | HELM_REPO_NAME="prometheus-community" 5 | NAMESPACE="monarch" 6 | MODULE_NAME="datadist" 7 | 8 | helm uninstall $MODULE_NAME \ 9 | --namespace $NAMESPACE \ -------------------------------------------------------------------------------- /data_store/README.md: -------------------------------------------------------------------------------- 1 | # Data Store 2 | S3 object storage using MinIO for usage with Thanos. 3 | MongoDB for storing configuration and metadata. 4 | 5 | # Installation 6 | 7 | ## minio 8 | ### install 9 | 10 | Use the community [Helm Charts](https://github.com/minio/minio/tree/master/helm/minio). 11 | 12 | ``` 13 | helm install minio -f minio-values.yaml minio/minio -n minio 14 | ``` 15 | 16 | ### minio client (optional) 17 | The minio client can be used to interact with the minio server. 18 | 19 | See the [documentation](https://min.io/docs/minio/linux/reference/minio-mc.html). 20 | ``` 21 | mc alias set minio https://minioserver.example.net ACCESS_KEY SECRET KEY 22 | 23 | ## mongodb 24 | 25 | ### install 26 | Deploy using Kustomize. -------------------------------------------------------------------------------- /data_store/install-mc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | curl https://dl.min.io/client/mc/release/linux-amd64/mc \ 3 | --create-dirs \ 4 | -o $HOME/minio-binaries/mc 5 | 6 | chmod +x $HOME/minio-binaries/mc 7 | export PATH=$PATH:$HOME/minio-binaries/ -------------------------------------------------------------------------------- /data_store/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MINIO_VERSION="5.0.15" 3 | HELM_REPO_URL="https://charts.min.io/" 4 | HELM_REPO_NAME="minio" 5 | NAMESPACE="monarch" 6 | MODULE_NAME="datastore" 7 | 8 | kubectl get namespace $NAMESPACE 2>/dev/null || kubectl create namespace $NAMESPACE 9 | helm repo add $HELM_REPO_NAME $HELM_REPO_URL || echo "Helm repo $HELM_REPO_NAME already exists." 10 | helm repo update 11 | 12 | helm upgrade --install $MODULE_NAME $HELM_REPO_NAME/minio \ 13 | --namespace $NAMESPACE \ 14 | --version $MINIO_VERSION \ 15 | --values minio/values.yaml 16 | 17 | kubectl apply -k mongodb/ -n $NAMESPACE -------------------------------------------------------------------------------- /data_store/minio/values.yaml: -------------------------------------------------------------------------------- 1 | service: 2 | type: NodePort 3 | nodePort: 30712 4 | 5 | consoleService: 6 | type: NodePort 7 | nodePort: 30713 8 | 9 | rootUser: "admin" 10 | rootPassword: "monarch-operator" 11 | 12 | mode: standalone 13 | 14 | replicas: 1 15 | persistence: 16 | enabled: true 17 | size: 5Gi 18 | 19 | resources: 20 | requests: 21 | memory: 250Mi 22 | 23 | buckets: 24 | - name: monarch-thanos 25 | -------------------------------------------------------------------------------- /data_store/mongodb/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - mongodb-deployment.yaml 6 | - mongodb-service.yaml 7 | - mongodb-serviceaccount.yaml 8 | 9 | namePrefix: 10 | datastore- -------------------------------------------------------------------------------- /data_store/mongodb/mongodb-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: mongodb 5 | labels: 6 | app.kubernetes.io/name: mongodb 7 | spec: 8 | serviceName: mongodb 9 | updateStrategy: 10 | type: RollingUpdate 11 | selector: 12 | matchLabels: 13 | app.kubernetes.io/name: mongodb 14 | template: 15 | metadata: 16 | labels: 17 | app.kubernetes.io/name: mongodb 18 | spec: 19 | serviceAccountName: mongodb 20 | securityContext: 21 | fsGroup: 1001 22 | sysctls: [] 23 | containers: 24 | - name: mongodb 25 | image: docker.io/bitnami/mongodb:4.4.4-debian-10-r0 26 | imagePullPolicy: "IfNotPresent" 27 | securityContext: 28 | runAsNonRoot: true 29 | runAsUser: 1001 30 | env: 31 | - name: BITNAMI_DEBUG 32 | value: "false" 33 | - name: ALLOW_EMPTY_PASSWORD 34 | value: "yes" 35 | - name: MONGODB_SYSTEM_LOG_VERBOSITY 36 | value: "0" 37 | - name: MONGODB_DISABLE_SYSTEM_LOG 38 | value: "no" 39 | - name: MONGODB_ENABLE_IPV6 40 | value: "no" 41 | - name: MONGODB_ENABLE_DIRECTORY_PER_DB 42 | value: "no" 43 | ports: 44 | - name: mongodb 45 | containerPort: 27017 46 | livenessProbe: 47 | exec: 48 | command: 49 | - mongo 50 | - --disableImplicitSessions 51 | - --eval 52 | - "db.adminCommand('ping')" 53 | initialDelaySeconds: 30 54 | periodSeconds: 10 55 | timeoutSeconds: 5 56 | successThreshold: 1 57 | failureThreshold: 6 58 | readinessProbe: 59 | exec: 60 | command: 61 | - bash 62 | - -ec 63 | - | 64 | mongo --disableImplicitSessions $TLS_OPTIONS --eval 'db.hello().isWritablePrimary || db.hello().secondary' | grep -q 'true' 65 | initialDelaySeconds: 5 66 | periodSeconds: 10 67 | timeoutSeconds: 5 68 | successThreshold: 1 69 | failureThreshold: 6 70 | resources: 71 | limits: {} 72 | requests: {} 73 | volumeMounts: 74 | - name: datadir 75 | mountPath: /bitnami/mongodb/data/db/ 76 | volumeClaimTemplates: 77 | - metadata: 78 | name: datadir 79 | spec: 80 | accessModes: 81 | - "ReadWriteOnce" 82 | resources: 83 | requests: 84 | storage: "6Gi" 85 | # storageClassName: local-storage -------------------------------------------------------------------------------- /data_store/mongodb/mongodb-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: mongodb 5 | labels: 6 | app.kubernetes.io/name: mongodb 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - name: mongodb 11 | port: 27017 12 | targetPort: mongodb 13 | selector: 14 | app.kubernetes.io/name: mongodb 15 | -------------------------------------------------------------------------------- /data_store/mongodb/mongodb-serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: mongodb 5 | labels: 6 | app.kubernetes.io/name: mongodb 7 | secrets: 8 | - name: mongodb 9 | -------------------------------------------------------------------------------- /data_store/test-data-store.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | print_header() { 3 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 4 | } 5 | 6 | print_success() { 7 | echo -e "\e[1;32m$1\e[0m" 8 | } 9 | 10 | print_error() { 11 | echo -e "\e[1;31mERROR: $1\e[0m" 12 | } 13 | 14 | print_info() { 15 | echo -e "\e[1;33mINFO: $1\e[0m" 16 | } 17 | 18 | print_subheader() { 19 | echo -e "\e[1;36m--- $1 ---\e[0m" 20 | } 21 | 22 | SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) 23 | 24 | # Set environment variables from .env file 25 | print_header "Testing Data Store (Monarch Core [1/5])" 26 | set -o allexport 27 | source "$SCRIPT_DIR/../.env" 28 | set +o allexport 29 | 30 | # Check if the necessary environment variables are set 31 | if [ -z "$MONARCH_MINIO_ENDPOINT" ] || [ -z "$MONARCH_MINIO_ACCESS_KEY" ] || [ -z "$MONARCH_MINIO_SECRET_KEY" ]; then 32 | print_error "One or more required environment variables are missing (MONARCH_MINIO_ENDPOINT, MONARCH_MINIO_ACCESS_KEY, MONARCH_MINIO_SECRET_KEY). Please check the .env file." 33 | exit 1 34 | fi 35 | 36 | # Add MinIO binary directory to the PATH 37 | export PATH=$PATH:$HOME/minio-binaries/ 38 | 39 | # Set the MinIO alias and perform a health check 40 | mc alias set myminio http://$MONARCH_MINIO_ENDPOINT $MONARCH_MINIO_ACCESS_KEY $MONARCH_MINIO_SECRET_KEY 41 | 42 | # Check MinIO admin info 43 | print_subheader "Fetching MinIO admin info" 44 | admin_info=$(mc admin info myminio) 45 | 46 | if [ -n "$admin_info" ]; then 47 | print_success "Successfully retrieved MinIO admin info." 48 | print_info "$admin_info" 49 | else 50 | print_error "Failed to fetch MinIO admin info." 51 | exit 1 52 | fi 53 | 54 | print_subheader "Listing S3 buckets" 55 | bucket_contents=$(mc ls --recursive myminio) 56 | 57 | if [ -n "$bucket_contents" ]; then 58 | print_success "Successfully listed S3 buckets." 59 | print_info "$bucket_contents" 60 | echo "Note that monitoring data is uploaded to S3 buckets every 2 hours." 61 | else 62 | print_info "Failed to list buckets. Note that buckets may be empty before 2 hours." 63 | exit 1 64 | fi -------------------------------------------------------------------------------- /data_store/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | KUBE_PROMETHEUS_STACK_VER="51.9.4" 3 | HELM_REPO_URL="https://prometheus-community.github.io/helm-charts" 4 | HELM_REPO_NAME="prometheus-community" 5 | NAMESPACE="monarch" 6 | MODULE_NAME="datastore" 7 | 8 | helm uninstall $MODULE_NAME \ 9 | --namespace $NAMESPACE \ 10 | 11 | kubectl delete -k mongodb/ -n $NAMESPACE -------------------------------------------------------------------------------- /data_visualization/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | CHART_VERSION="8.6.0" 3 | HELM_REPO_URL="https://grafana.github.io/helm-charts" 4 | HELM_REPO_NAME="grafana" 5 | HELM_CHART_NAME="grafana" 6 | NAMESPACE="monarch" 7 | MODULE_NAME="dataviz" 8 | 9 | kubectl get namespace $NAMESPACE 2>/dev/null || kubectl create namespace $NAMESPACE 10 | helm repo add $HELM_REPO_NAME $HELM_REPO_URL || echo "Helm repo $HELM_REPO_NAME already exists." 11 | helm repo update 12 | set -o allexport; source ../.env; set +o allexport 13 | 14 | helm upgrade --install $MODULE_NAME $HELM_REPO_NAME/grafana \ 15 | --namespace $NAMESPACE \ 16 | --version $CHART_VERSION \ 17 | --values <(envsubst < values.yaml) 18 | 19 | kubectl apply -k . -------------------------------------------------------------------------------- /data_visualization/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | namespace: monarch 5 | 6 | configMapGenerator: 7 | - name: monarch-dashboard-configmap 8 | behavior: create 9 | files: 10 | - dashboards/monarch-dashboard.json 11 | options: 12 | disableNameSuffixHash: true 13 | generatorOptions: 14 | labels: 15 | grafana_dashboard: "1" -------------------------------------------------------------------------------- /data_visualization/test-data-visualization.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | GRAFANA_URL="http://localhost:32005" 3 | DASHBOARD_UID="f1471646-a245-4f23-a3e1-452baa6558c8" 4 | USERNAME="admin" 5 | PASSWORD="monarch-operator" 6 | 7 | 8 | print_subheader() { 9 | echo -e "\e[1;36m--- $1 ---\e[0m" 10 | } 11 | 12 | print_header() { 13 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 14 | } 15 | 16 | print_success() { 17 | echo -e "\e[1;32m$1\e[0m" 18 | } 19 | 20 | print_error() { 21 | echo -e "\e[1;31mERROR: $1\e[0m" 22 | } 23 | 24 | print_info() { 25 | echo -e "\e[1;33mINFO: $1\e[0m" 26 | } 27 | 28 | 29 | print_header "Testing Data Visualization (Monarch Core [3/5])" 30 | print_subheader "Checking Monarch Dashboard with UID: $DASHBOARD_UID" 31 | response=$(curl -s -u "$USERNAME:$PASSWORD" \ 32 | -H "Content-Type: application/json" \ 33 | "$GRAFANA_URL/api/search?dashboardUIDs=$DASHBOARD_UID") 34 | 35 | # Check if the response is empty or contains dashboard information 36 | if [ -n "$response" ] && [ "$response" != "[]" ]; then 37 | echo "Dashboard found for UID $DASHBOARD_UID:" 38 | echo "$response" | jq '.' 39 | else 40 | echo "No dashboard found with UID: $DASHBOARD_UID." 41 | fi -------------------------------------------------------------------------------- /data_visualization/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | CHART_VERSION="8.6.0" 3 | HELM_REPO_URL="https://grafana.github.io/helm-charts" 4 | HELM_REPO_NAME="grafana" 5 | HELM_CHART_NAME="grafana" 6 | NAMESPACE="monarch" 7 | MODULE_NAME="dataviz" 8 | 9 | helm uninstall $MODULE_NAME \ 10 | --namespace $NAMESPACE \ 11 | 12 | kubectl delete -k . -------------------------------------------------------------------------------- /data_visualization/values.yaml: -------------------------------------------------------------------------------- 1 | service: 2 | enabled: true 3 | type: NodePort 4 | nodePort: 32005 5 | 6 | adminUser: admin 7 | adminPassword: monarch-operator 8 | 9 | datasources: 10 | datasources.yaml: 11 | apiVersion: 1 12 | datasources: 13 | - name: monarch-thanos 14 | type: prometheus 15 | url: http://${NODE_IP}:31004 16 | isDefault: true 17 | uid: monarch-thanos 18 | editable: true 19 | jsonData: 20 | timeInterval: 1s 21 | 22 | dashboardProviders: 23 | dashboardproviders.yaml: 24 | apiVersion: 1 25 | providers: 26 | - name: 'default' 27 | orgId: 1 28 | folder: '' 29 | type: file 30 | disableDeletion: false 31 | editable: true 32 | options: 33 | path: /var/lib/grafana/dashboards/default 34 | 35 | dashboardsConfigMaps: 36 | default: monarch-dashboard-configmap 37 | 38 | grafana.ini: 39 | dashboards: 40 | min_refresh_interval: 1s 41 | users: 42 | default_theme: light 43 | viewers_can_edit: true 44 | -------------------------------------------------------------------------------- /deploy-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./replace-node-ip.sh 3 | ./deploy-external.sh 4 | ./deploy-monarch-core.sh 5 | ./deploy-monarch-nss.sh -------------------------------------------------------------------------------- /deploy-external.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Function to print in color 4 | print_header() { 5 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 6 | } 7 | 8 | print_success() { 9 | echo -e "\e[1;32m$1\e[0m" 10 | } 11 | 12 | print_error() { 13 | echo -e "\e[1;31mERROR: $1\e[0m" 14 | } 15 | 16 | 17 | # Function to wait for a pod to be ready based on its label 18 | wait_for_pod_ready() { 19 | local label_key=$1 20 | local label_value=$2 21 | echo "Waiting for pod with label $label_key=$label_value to be ready in namespace $NAMESPACE..." 22 | 23 | while [ "$(kubectl get pods -n "$NAMESPACE" -l="$label_key=$label_value" -o jsonpath='{.items[*].status.containerStatuses[0].ready}')" != "true" ]; do 24 | sleep 5 25 | echo "Waiting for pod $label_value to be ready..." 26 | done 27 | print_success "Pod $label_value is ready." 28 | } 29 | 30 | # Set the namespace for Monarch 31 | NAMESPACE="monarch" 32 | USER="$(whoami)" 33 | WORKING_DIR="$(pwd)" 34 | 35 | 36 | # Check if the namespace exists and create it if not 37 | print_header "Checking if namespace '$NAMESPACE' exists" 38 | kubectl get namespace $NAMESPACE 2>/dev/null || { 39 | print_error "Namespace '$NAMESPACE' not found. Creating it now..." 40 | kubectl create namespace $NAMESPACE 41 | print_success "Namespace '$NAMESPACE' created." 42 | } 43 | 44 | 45 | print_header "Installing Python dependencies" 46 | pip3 install -r requirements.txt 47 | print_success "Python dependencies installed." 48 | 49 | 50 | print_header "Deploying Monarch external components (i.e., Service Orchestrator and NFV Orchestrator)" 51 | cd $WORKING_DIR/service_orchestrator 52 | ./install.sh 53 | 54 | cd $WORKING_DIR/nfv_orchestrator 55 | ./install.sh 56 | 57 | print_success "External components deployed." -------------------------------------------------------------------------------- /deploy-monarch-core.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Function to print in color 4 | print_header() { 5 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 6 | } 7 | 8 | print_success() { 9 | echo -e "\e[1;32m$1\e[0m" 10 | } 11 | 12 | print_error() { 13 | echo -e "\e[1;31mERROR: $1\e[0m" 14 | } 15 | 16 | 17 | # Function to wait for a pod to be ready based on its label 18 | wait_for_pod_ready() { 19 | local label_key=$1 20 | local label_value=$2 21 | echo "Waiting for pod with label $label_key=$label_value to be ready in namespace $NAMESPACE..." 22 | 23 | while [ "$(kubectl get pods -n "$NAMESPACE" -l="$label_key=$label_value" -o jsonpath='{.items[*].status.containerStatuses[0].ready}')" != "true" ]; do 24 | sleep 5 25 | echo "Waiting for pod $label_value to be ready..." 26 | done 27 | print_success "Pod $label_value is ready." 28 | } 29 | 30 | wait_for_pod_running() { 31 | local label_key=$1 32 | local label_value=$2 33 | echo "Waiting for pod with label $label_key=$label_value to be running in namespace $NAMESPACE..." 34 | 35 | # Check if the pod exists 36 | pod_count=$(kubectl get pods -n "$NAMESPACE" -l "$label_key=$label_value" --no-headers | wc -l) 37 | 38 | if [ "$pod_count" -eq 0 ]; then 39 | print_error "No pods found with label $label_key=$label_value in namespace $NAMESPACE." 40 | return 1 41 | fi 42 | 43 | # Wait for the pod to be in Running state 44 | while : ; do 45 | # Get the pod status 46 | pod_status=$(kubectl get pods -n "$NAMESPACE" -l "$label_key=$label_value" -o jsonpath='{.items[*].status.phase}') 47 | 48 | # Check if the pod is in 'Running' state 49 | if [[ "$pod_status" =~ "Running" ]]; then 50 | print_success "Pod $label_value is now running." 51 | break 52 | else 53 | echo "Pod $label_value is not running yet. Waiting..." 54 | sleep 5 55 | fi 56 | done 57 | } 58 | 59 | # Set the namespace for Monarch 60 | NAMESPACE="monarch" 61 | USER="$(whoami)" 62 | WORKING_DIR="$(pwd)" 63 | 64 | 65 | print_header "Deploying Data Store (Monarch Core [1/5])" 66 | cd $WORKING_DIR/data_store 67 | ./install.sh 68 | wait_for_pod_ready "app" "minio" 69 | wait_for_pod_ready "app.kubernetes.io/name" "mongodb" 70 | ./install-mc.sh 71 | print_success "Data Store deployed." 72 | 73 | print_header "Deploying Data Distribution (Monarch Core [2/5])" 74 | cd $WORKING_DIR/data_distribution 75 | ./install.sh 76 | wait_for_pod_ready "app.kubernetes.io/component" "storegateway" 77 | wait_for_pod_ready "app.kubernetes.io/component" "receive" 78 | print_success "Data Distribution deployed." 79 | 80 | 81 | print_header "Deploying Data Visualization (Monarch Core [3/5])" 82 | cd $WORKING_DIR/data_visualization 83 | ./install.sh 84 | wait_for_pod_ready "app.kubernetes.io/name" "grafana" 85 | print_success "Data Visualization deployed." 86 | 87 | 88 | print_header "Deploying Monitoring Manager (Monarch Core [4/5])" 89 | cd $WORKING_DIR/monitoring_manager 90 | ./install.sh 91 | wait_for_pod_ready "component" "monitoring-manager" 92 | print_success "Monitoring manager deployed." 93 | 94 | print_header "Deploying Request Translator (Monarch Core [5/5])" 95 | cd $WORKING_DIR/request_translator 96 | ./install.sh 97 | wait_for_pod_ready "component" "request-translator" 98 | print_success "Request Translator deployed." -------------------------------------------------------------------------------- /deploy-monarch-nss.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | print_header() { 4 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 5 | } 6 | 7 | print_success() { 8 | echo -e "\e[1;32m$1\e[0m" 9 | } 10 | 11 | print_error() { 12 | echo -e "\e[1;31mERROR: $1\e[0m" 13 | } 14 | 15 | 16 | # Function to wait for a pod to be ready based on its label 17 | wait_for_pod_ready() { 18 | local label_key=$1 19 | local label_value=$2 20 | echo "Waiting for pod with label $label_key=$label_value to be ready in namespace $NAMESPACE..." 21 | 22 | while [ "$(kubectl get pods -n "$NAMESPACE" -l="$label_key=$label_value" -o jsonpath='{.items[*].status.containerStatuses[0].ready}')" != "true" ]; do 23 | sleep 5 24 | echo "Waiting for pod $label_value to be ready..." 25 | done 26 | print_success "Pod $label_value is ready." 27 | } 28 | 29 | wait_for_pod_running() { 30 | local label_key=$1 31 | local label_value=$2 32 | echo "Waiting for pod with label $label_key=$label_value to be running in namespace $NAMESPACE..." 33 | 34 | # Check if the pod exists 35 | pod_count=$(kubectl get pods -n "$NAMESPACE" -l "$label_key=$label_value" --no-headers | wc -l) 36 | 37 | if [ "$pod_count" -eq 0 ]; then 38 | print_error "No pods found with label $label_key=$label_value in namespace $NAMESPACE." 39 | return 1 40 | fi 41 | 42 | # Wait for the pod to be in Running state 43 | while : ; do 44 | # Get the pod status 45 | pod_status=$(kubectl get pods -n "$NAMESPACE" -l "$label_key=$label_value" -o jsonpath='{.items[*].status.phase}') 46 | 47 | # Check if the pod is in 'Running' state 48 | if [[ "$pod_status" =~ "Running" ]]; then 49 | print_success "Pod $label_value is now running." 50 | break 51 | else 52 | echo "Pod $label_value is not running yet. Waiting..." 53 | sleep 5 54 | fi 55 | done 56 | } 57 | 58 | # Set the namespace for Monarch 59 | NAMESPACE="monarch" 60 | USER="$(whoami)" 61 | WORKING_DIR="$(pwd)" 62 | 63 | 64 | print_header "Deploying NSSDC (Monarch NSS [1/1])" 65 | cd $WORKING_DIR/nssdc 66 | ./install.sh 67 | wait_for_pod_ready "app.kubernetes.io/name" "prometheus" 68 | print_success "NSSDC deployed." 69 | -------------------------------------------------------------------------------- /get-node-ip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run kubectl get pods with JSON output and extract the IP of the control plane node 4 | control_plane_ip=$(kubectl get nodes -o json | jq -r '.items[0].status.addresses[0].address') 5 | echo $control_plane_ip -------------------------------------------------------------------------------- /images/cni-out-of-ips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/cni-out-of-ips.png -------------------------------------------------------------------------------- /images/core-test-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/core-test-2.png -------------------------------------------------------------------------------- /images/data-distribution-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/data-distribution-up.png -------------------------------------------------------------------------------- /images/data-source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/data-source.png -------------------------------------------------------------------------------- /images/data-store-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/data-store-up.png -------------------------------------------------------------------------------- /images/datasource-config-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/datasource-config-1.png -------------------------------------------------------------------------------- /images/datasource-config-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/datasource-config-2.png -------------------------------------------------------------------------------- /images/dataviz-gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/dataviz-gui.png -------------------------------------------------------------------------------- /images/dataviz-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/dataviz-up.png -------------------------------------------------------------------------------- /images/deploying-ueransim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/deploying-ueransim.png -------------------------------------------------------------------------------- /images/external-components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/external-components.png -------------------------------------------------------------------------------- /images/grafana-edit-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/grafana-edit-1.png -------------------------------------------------------------------------------- /images/grafana-edit-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/grafana-edit-2.png -------------------------------------------------------------------------------- /images/grafana-metrics-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/grafana-metrics-browser.png -------------------------------------------------------------------------------- /images/minio-access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/minio-access.png -------------------------------------------------------------------------------- /images/minio-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/minio-keys.png -------------------------------------------------------------------------------- /images/monarch-conceptual-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/monarch-conceptual-architecture.png -------------------------------------------------------------------------------- /images/monarch-core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/monarch-core.png -------------------------------------------------------------------------------- /images/monarch-dashboard-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/monarch-dashboard-1.png -------------------------------------------------------------------------------- /images/monarch-dashboard-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/monarch-dashboard-2.png -------------------------------------------------------------------------------- /images/monarch-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/monarch-dashboard.png -------------------------------------------------------------------------------- /images/monitoring-directives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/monitoring-directives.png -------------------------------------------------------------------------------- /images/monitoring-manager-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/monitoring-manager-up.png -------------------------------------------------------------------------------- /images/network-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/network-up.png -------------------------------------------------------------------------------- /images/nssdc-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/nssdc-up.png -------------------------------------------------------------------------------- /images/prometheus-crash-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/prometheus-crash-1.png -------------------------------------------------------------------------------- /images/prometheus-tsdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/prometheus-tsdb.png -------------------------------------------------------------------------------- /images/request-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/request-success.png -------------------------------------------------------------------------------- /images/request-translator-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/request-translator-up.png -------------------------------------------------------------------------------- /images/rogers-demo-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/rogers-demo-dashboard.png -------------------------------------------------------------------------------- /images/test-external.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/test-external.png -------------------------------------------------------------------------------- /images/test-monarch-core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/test-monarch-core.png -------------------------------------------------------------------------------- /images/test-monarch-nss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/test-monarch-nss.png -------------------------------------------------------------------------------- /images/thanos-stores.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/thanos-stores.png -------------------------------------------------------------------------------- /images/watch-monarch-core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/images/watch-monarch-core.png -------------------------------------------------------------------------------- /kpi_computation/README.md: -------------------------------------------------------------------------------- 1 | # KPI Computation 2 | This component is responsible for computing Key Performance Indicators (KPIs) based on the data exported by the MDEs. 3 | -------------------------------------------------------------------------------- /kpi_computation/check-kpi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | kubectl get pods -n monarch -l app=monarch,component=kpi-calculator -o json | jq .items[].metadata.name -------------------------------------------------------------------------------- /kpi_computation/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NAMESPACE="monarch" 3 | MODULE_NAME="kpi-computation" 4 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | cd "$SCRIPT_DIR" 6 | set -o allexport; source ../.env; set +o allexport 7 | kubectl get namespace $NAMESPACE 2>/dev/null || kubectl create namespace $NAMESPACE 8 | envsubst < standard/kpi_calculator.yaml | kubectl apply -f - 9 | 10 | print_success() { 11 | echo -e "\e[1;32m$1\e[0m" 12 | } 13 | 14 | # Function to wait for a pod to be ready based on its label 15 | wait_for_pod_ready() { 16 | local label_key=$1 17 | local label_value=$2 18 | echo "Waiting for pod with label $label_key=$label_value to be ready in namespace $NAMESPACE..." 19 | 20 | while [ "$(kubectl get pods -n "$NAMESPACE" -l="$label_key=$label_value" -o jsonpath='{.items[*].status.containerStatuses[0].ready}')" != "true" ]; do 21 | sleep 5 22 | echo "Waiting for pod $label_value to be ready..." 23 | done 24 | print_success "Pod $label_value is ready." 25 | } 26 | 27 | wait_for_pod_ready "component" "kpi-calculator" -------------------------------------------------------------------------------- /kpi_computation/otel/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | LABEL maintainer="Niloy Saha " 3 | LABEL description="Slice KPI Calculator v1.0.0 for Open5GS" 4 | 5 | 6 | RUN mkdir -p /exporter/ 7 | 8 | WORKDIR /exporter 9 | COPY /app/requirements.txt ./ 10 | RUN pip install -r requirements.txt 11 | COPY /app/* ./ 12 | 13 | EXPOSE 9000 14 | 15 | CMD python3 kpi_calculator.py -------------------------------------------------------------------------------- /kpi_computation/otel/app/kpi_calculator.py: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------------------- 2 | # Author: Niloy Saha 3 | # Email: niloysaha.ns@gmail.com 4 | # version ='1.0.0' 5 | # --------------------------------------------------------------------------- 6 | """ 7 | Prometheus exporter which exports slice throughput KPI. 8 | For use with the 5G-MONARCH project and Open5GS. 9 | """ 10 | import os 11 | import logging 12 | import time 13 | import requests 14 | import prometheus_client as prom 15 | import argparse 16 | 17 | from dotenv import load_dotenv 18 | 19 | load_dotenv() 20 | MONARCH_THANOS_URL = os.getenv("MONARCH_THANOS_URL") 21 | DEFAULT_UPDATE_PERIOD = 1 22 | UPDATE_PERIOD = int(os.environ.get('UPDATE_PERIOD', DEFAULT_UPDATE_PERIOD)) 23 | EXPORTER_PORT = 9000 24 | TIME_RANGE = os.getenv("TIME_RANGE", "5s") 25 | 26 | 27 | # Prometheus variables 28 | SLICE_THROUGHPUT = prom.Gauge('slice_throughput', 'throughput per slice (bits/sec)', ['snssai', 'seid', 'direction']) 29 | # get rid of bloat 30 | prom.REGISTRY.unregister(prom.PROCESS_COLLECTOR) 31 | prom.REGISTRY.unregister(prom.PLATFORM_COLLECTOR) 32 | prom.REGISTRY.unregister(prom.GC_COLLECTOR) 33 | 34 | def query_prometheus(params, url): 35 | """ 36 | Query Prometheus using requests and return value. 37 | params: The parameters for the Prometheus query. 38 | url: The URL of the Prometheus server. 39 | Returns: The result of the Prometheus query. 40 | """ 41 | try: 42 | r = requests.get(url + '/api/v1/query', params) 43 | data = r.json() 44 | 45 | results = data["data"]["result"] 46 | return results 47 | 48 | except requests.exceptions.RequestException as e: 49 | log.error(f"Failed to query Prometheus: {e}") 50 | except (KeyError, IndexError, ValueError) as e: 51 | log.error(f"Failed to parse Prometheus response: {e}") 52 | log.warning("No data available!") 53 | 54 | def get_slice_throughput_per_seid_and_direction(snssai, direction): 55 | """ 56 | Queries both the SMF and UPF to get the throughput per SEID and direction. 57 | Returns a dictionary of the form {seid: value (bits/sec)} 58 | """ 59 | time_range = TIME_RANGE 60 | throughput_per_seid = {} # {seid: value (bits/sec)} 61 | 62 | direction_mapping = { 63 | "uplink": "outdatavolumen3upf", 64 | "downlink": "indatavolumen3upf" 65 | } 66 | 67 | if direction not in direction_mapping: 68 | log.error("Invalid direction") 69 | return 70 | 71 | query = f'sum by (seid) (rate(monarch_fivegs_ep_n3_gtp_{direction_mapping[direction]}_seid_total[{time_range}]) * on (seid) group_right sum(monarch_fivegs_smffunction_sm_seid_session{{snssai="{snssai}"}}) by (seid, snssai)) * 8' 72 | log.debug(query) 73 | params = {'query': query} 74 | results = query_prometheus(params, MONARCH_THANOS_URL) 75 | 76 | if results: 77 | for result in results: 78 | seid = result["metric"]["seid"] 79 | value = float(result["value"][1]) 80 | throughput_per_seid[seid] = value 81 | 82 | return throughput_per_seid 83 | 84 | 85 | def get_active_snssais(): 86 | """ 87 | Return a list of active SNSSAIs from the SMF. 88 | """ 89 | time_range = TIME_RANGE 90 | query = f'sum by (snssai) (rate(monarch_fivegs_smffunction_sm_seid_session[{time_range}]))' 91 | log.debug(query) 92 | params = {'query': query} 93 | results = query_prometheus(params, MONARCH_THANOS_URL) 94 | active_snssais = [result["metric"]["snssai"] for result in results] 95 | return active_snssais 96 | 97 | def main(): 98 | log.info("Starting Prometheus server on port {}".format(EXPORTER_PORT)) 99 | 100 | if not MONARCH_THANOS_URL: 101 | log.error("MONARCH_THANOS_URL is not set") 102 | return 103 | 104 | log.info(f"Monarch Thanos URL: {MONARCH_THANOS_URL}") 105 | log.info(f"Time range: {TIME_RANGE}") 106 | log.info(f"Update period: {UPDATE_PERIOD}") 107 | prom.start_http_server(EXPORTER_PORT) 108 | 109 | while True: 110 | try: 111 | run_kpi_computation() 112 | except Exception as e: 113 | log.error(f"Failing to run KPI computation: {e}") 114 | time.sleep(UPDATE_PERIOD) 115 | 116 | def export_to_prometheus(snssai, seid, direction, value): 117 | value_mbits = round(value / 10 ** 6, 6) 118 | log.info(f"SNSSAI={snssai} | SEID={seid} | DIR={direction:8s} | RATE (Mbps)={value_mbits}") 119 | SLICE_THROUGHPUT.labels(snssai=snssai, seid=seid, direction=direction).set(value) 120 | 121 | def run_kpi_computation(): 122 | directions = ["uplink", "downlink"] 123 | active_snssais = get_active_snssais() 124 | if not active_snssais: 125 | log.warning("No active SNSSAIs found") 126 | return 127 | for snssai in active_snssais: 128 | for direction in directions: 129 | throughput_per_seid = get_slice_throughput_per_seid_and_direction(snssai, direction) 130 | for seid, value in throughput_per_seid.items(): 131 | export_to_prometheus(snssai, seid, direction, value) 132 | 133 | 134 | if __name__ == "__main__": 135 | parser = argparse.ArgumentParser(description='KPI calculator.') 136 | parser.add_argument('--log', default='info', help='Log verbosity level. Default is "info". Options are "debug", "info", "warning", "error", "critical".') 137 | 138 | args = parser.parse_args() 139 | 140 | # Convert log level from string to logging level 141 | log_level = getattr(logging, args.log.upper(), None) 142 | if not isinstance(log_level, int): 143 | raise ValueError(f'Invalid log level: {args.log}') 144 | 145 | # setup logger for console output 146 | log = logging.getLogger(__name__) 147 | log.setLevel(log_level) 148 | console_handler = logging.StreamHandler() 149 | console_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')) 150 | log.addHandler(console_handler) 151 | 152 | main() 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /kpi_computation/otel/app/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | prometheus_client 3 | python-dotenv -------------------------------------------------------------------------------- /kpi_computation/otel/kpi_calculator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: kpi-calculator 5 | namespace: monarch 6 | labels: 7 | app: monarch 8 | component: kpi-calculator 9 | spec: 10 | selector: 11 | matchLabels: 12 | app: monarch 13 | component: kpi-calculator 14 | replicas: 1 15 | template: 16 | metadata: 17 | labels: 18 | app: monarch 19 | component: kpi-calculator 20 | spec: 21 | containers: 22 | - image: ghcr.io/niloysh/kpi-calculator-open5gs:v1.0.0 23 | name: kpi-calculator 24 | imagePullPolicy: Always 25 | ports: 26 | - name: metrics 27 | containerPort: 9000 28 | env: 29 | - name: UPDATE_PERIOD 30 | value: "1" 31 | - name: MONARCH_THANOS_URL 32 | value: "${MONARCH_THANOS_URL}" 33 | - name: TIME_RANGE 34 | value: "5s" 35 | command: ["/bin/bash", "-c", "--"] 36 | args: ["python -u kpi_calculator.py"] 37 | resources: 38 | requests: 39 | memory: "100Mi" 40 | cpu: "100m" 41 | limits: 42 | memory: "200Mi" 43 | cpu: "200m" 44 | restartPolicy: Always 45 | --- 46 | apiVersion: v1 47 | kind: Service 48 | metadata: 49 | name: kpi-calculator-service 50 | namespace: monarch 51 | labels: 52 | app: monarch 53 | component: kpi-calculator 54 | spec: 55 | ports: 56 | - name: metrics # expose metrics port 57 | port: 9000 # defined in chart 58 | targetPort: metrics # port name in pod 59 | selector: 60 | app: monarch # target pods 61 | component: kpi-calculator 62 | --- 63 | apiVersion: monitoring.coreos.com/v1 64 | kind: ServiceMonitor 65 | metadata: 66 | name: kpi-calculator-servicemonitor 67 | namespace: monarch 68 | labels: 69 | app: monarch 70 | component: kpi-calculator 71 | spec: 72 | namespaceSelector: 73 | any: true # important otherwise this is not picked up 74 | selector: 75 | matchLabels: 76 | app: monarch # target service 77 | component: kpi-calculator 78 | endpoints: 79 | - port: metrics 80 | interval: 1s 81 | -------------------------------------------------------------------------------- /kpi_computation/standard/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | LABEL maintainer="Niloy Saha " 3 | LABEL description="Slice KPI Calculator v1.0.0 for Open5GS" 4 | 5 | 6 | RUN mkdir -p /exporter/ 7 | 8 | WORKDIR /exporter 9 | COPY /app/requirements.txt ./ 10 | RUN pip install -r requirements.txt 11 | COPY /app/* ./ 12 | 13 | EXPOSE 9000 14 | 15 | CMD python3 kpi_calculator.py -------------------------------------------------------------------------------- /kpi_computation/standard/app/kpi_calculator.py: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------------------- 2 | # Author: Niloy Saha 3 | # Email: niloysaha.ns@gmail.com 4 | # version ='1.0.0' 5 | # --------------------------------------------------------------------------- 6 | """ 7 | Prometheus exporter which exports slice throughput KPI. 8 | For use with the 5G-MONARCH project and Open5GS. 9 | """ 10 | import os 11 | import logging 12 | import time 13 | import requests 14 | import prometheus_client as prom 15 | import argparse 16 | 17 | from dotenv import load_dotenv 18 | 19 | load_dotenv() 20 | MONARCH_THANOS_URL = os.getenv("MONARCH_THANOS_URL") 21 | DEFAULT_UPDATE_PERIOD = 1 22 | UPDATE_PERIOD = int(os.environ.get('UPDATE_PERIOD', DEFAULT_UPDATE_PERIOD)) 23 | EXPORTER_PORT = 9000 24 | TIME_RANGE = os.getenv("TIME_RANGE", "5s") 25 | 26 | 27 | # Prometheus variables 28 | SLICE_THROUGHPUT = prom.Gauge('slice_throughput', 'throughput per slice (bits/sec)', ['snssai', 'seid', 'direction']) 29 | # get rid of bloat 30 | prom.REGISTRY.unregister(prom.PROCESS_COLLECTOR) 31 | prom.REGISTRY.unregister(prom.PLATFORM_COLLECTOR) 32 | prom.REGISTRY.unregister(prom.GC_COLLECTOR) 33 | 34 | def query_prometheus(params, url): 35 | """ 36 | Query Prometheus using requests and return value. 37 | params: The parameters for the Prometheus query. 38 | url: The URL of the Prometheus server. 39 | Returns: The result of the Prometheus query. 40 | """ 41 | try: 42 | r = requests.get(url + '/api/v1/query', params) 43 | data = r.json() 44 | 45 | results = data["data"]["result"] 46 | return results 47 | 48 | except requests.exceptions.RequestException as e: 49 | log.error(f"Failed to query Prometheus: {e}") 50 | except (KeyError, IndexError, ValueError) as e: 51 | log.error(f"Failed to parse Prometheus response: {e}") 52 | log.warning("No data available!") 53 | 54 | def get_slice_throughput_per_seid_and_direction(snssai, direction): 55 | """ 56 | Queries both the SMF and UPF to get the throughput per SEID and direction. 57 | Returns a dictionary of the form {seid: value (bits/sec)} 58 | """ 59 | time_range = TIME_RANGE 60 | throughput_per_seid = {} # {seid: value (bits/sec)} 61 | 62 | direction_mapping = { 63 | "uplink": "outdatavolumen3upf", 64 | "downlink": "indatavolumen3upf" 65 | } 66 | 67 | if direction not in direction_mapping: 68 | log.error("Invalid direction") 69 | return 70 | 71 | query = f'sum by (seid) (rate(fivegs_ep_n3_gtp_{direction_mapping[direction]}_seid[{time_range}]) * on (seid) group_right sum(fivegs_smffunction_sm_seid_session{{snssai="{snssai}"}}) by (seid, snssai)) * 8' 72 | log.debug(query) 73 | params = {'query': query} 74 | results = query_prometheus(params, MONARCH_THANOS_URL) 75 | 76 | if results: 77 | for result in results: 78 | seid = result["metric"]["seid"] 79 | value = float(result["value"][1]) 80 | throughput_per_seid[seid] = value 81 | 82 | return throughput_per_seid 83 | 84 | 85 | def get_active_snssais(): 86 | """ 87 | Return a list of active SNSSAIs from the SMF. 88 | """ 89 | time_range = TIME_RANGE 90 | query = f'sum by (snssai) (rate(fivegs_smffunction_sm_seid_session[{time_range}]))' 91 | log.debug(query) 92 | params = {'query': query} 93 | results = query_prometheus(params, MONARCH_THANOS_URL) 94 | active_snssais = [result["metric"]["snssai"] for result in results] 95 | return active_snssais 96 | 97 | def main(): 98 | log.info("Starting Prometheus server on port {}".format(EXPORTER_PORT)) 99 | 100 | if not MONARCH_THANOS_URL: 101 | log.error("MONARCH_THANOS_URL is not set") 102 | return 103 | 104 | log.info(f"Monarch Thanos URL: {MONARCH_THANOS_URL}") 105 | log.info(f"Time range: {TIME_RANGE}") 106 | log.info(f"Update period: {UPDATE_PERIOD}") 107 | prom.start_http_server(EXPORTER_PORT) 108 | 109 | while True: 110 | try: 111 | run_kpi_computation() 112 | except Exception as e: 113 | log.error(f"Failing to run KPI computation: {e}") 114 | time.sleep(UPDATE_PERIOD) 115 | 116 | def export_to_prometheus(snssai, seid, direction, value): 117 | value_mbits = round(value / 10 ** 6, 6) 118 | log.info(f"SNSSAI={snssai} | SEID={seid} | DIR={direction:8s} | RATE (Mbps)={value_mbits}") 119 | SLICE_THROUGHPUT.labels(snssai=snssai, seid=seid, direction=direction).set(value) 120 | 121 | def run_kpi_computation(): 122 | directions = ["uplink", "downlink"] 123 | active_snssais = get_active_snssais() 124 | if not active_snssais: 125 | log.warning("No active SNSSAIs found") 126 | return 127 | 128 | log.debug(f"Active SNSSAIs: {active_snssais}") 129 | for snssai in active_snssais: 130 | for direction in directions: 131 | throughput_per_seid = get_slice_throughput_per_seid_and_direction(snssai, direction) 132 | for seid, value in throughput_per_seid.items(): 133 | export_to_prometheus(snssai, seid, direction, value) 134 | 135 | 136 | if __name__ == "__main__": 137 | parser = argparse.ArgumentParser(description='KPI calculator.') 138 | parser.add_argument('--log', default='info', help='Log verbosity level. Default is "info". Options are "debug", "info", "warning", "error", "critical".') 139 | 140 | args = parser.parse_args() 141 | 142 | # Convert log level from string to logging level 143 | log_level = getattr(logging, args.log.upper(), None) 144 | if not isinstance(log_level, int): 145 | raise ValueError(f'Invalid log level: {args.log}') 146 | 147 | # setup logger for console output 148 | log = logging.getLogger(__name__) 149 | log.setLevel(log_level) 150 | console_handler = logging.StreamHandler() 151 | console_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')) 152 | log.addHandler(console_handler) 153 | 154 | main() 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /kpi_computation/standard/app/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | prometheus_client 3 | python-dotenv -------------------------------------------------------------------------------- /kpi_computation/standard/kpi_calculator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: kpi-calculator 5 | namespace: monarch 6 | labels: 7 | app: monarch 8 | component: kpi-calculator 9 | spec: 10 | selector: 11 | matchLabels: 12 | app: monarch 13 | component: kpi-calculator 14 | replicas: 1 15 | template: 16 | metadata: 17 | labels: 18 | app: monarch 19 | component: kpi-calculator 20 | spec: 21 | containers: 22 | - image: ghcr.io/niloysh/kpi-calculator-open5gs:v1.0.0-standard 23 | name: kpi-calculator 24 | imagePullPolicy: Always 25 | ports: 26 | - name: metrics 27 | containerPort: 9000 28 | env: 29 | - name: UPDATE_PERIOD 30 | value: "1" 31 | - name: MONARCH_THANOS_URL 32 | value: "${MONARCH_THANOS_URL}" 33 | - name: TIME_RANGE 34 | value: "30s" 35 | command: ["/bin/bash", "-c", "--"] 36 | args: ["python -u kpi_calculator.py"] 37 | resources: 38 | requests: 39 | memory: "100Mi" 40 | cpu: "100m" 41 | limits: 42 | memory: "200Mi" 43 | cpu: "200m" 44 | restartPolicy: Always 45 | --- 46 | apiVersion: v1 47 | kind: Service 48 | metadata: 49 | name: kpi-calculator-service 50 | namespace: monarch 51 | labels: 52 | app: monarch 53 | component: kpi-calculator 54 | annotations: 55 | prometheus.io/scrape: "true" 56 | prometheus.io.scheme: "http" 57 | prometheus.io/path: "/metrics" 58 | prometheus.io/port: "9000" 59 | spec: 60 | ports: 61 | - name: metrics # expose metrics port 62 | port: 9000 # defined in chart 63 | targetPort: metrics # port name in pod 64 | selector: 65 | app: monarch # target pods 66 | component: kpi-calculator 67 | --- 68 | -------------------------------------------------------------------------------- /kpi_computation/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NAMESPACE="monarch" 3 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | cd "$SCRIPT_DIR" 5 | kubectl delete --wait=true -f standard/kpi_calculator.yaml 6 | -------------------------------------------------------------------------------- /labs/images/5g-targets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/5g-targets.png -------------------------------------------------------------------------------- /labs/images/aggregration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/aggregration.png -------------------------------------------------------------------------------- /labs/images/beyond-monarch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/beyond-monarch.png -------------------------------------------------------------------------------- /labs/images/deriv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/deriv.png -------------------------------------------------------------------------------- /labs/images/expression-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/expression-browser.png -------------------------------------------------------------------------------- /labs/images/instrumentationx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/instrumentationx.png -------------------------------------------------------------------------------- /labs/images/prometheus-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/prometheus-1.png -------------------------------------------------------------------------------- /labs/images/prometheus-exporter-target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/prometheus-exporter-target.png -------------------------------------------------------------------------------- /labs/images/prometheus-interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/prometheus-interface.png -------------------------------------------------------------------------------- /labs/images/prometheus-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/prometheus-logo.png -------------------------------------------------------------------------------- /labs/images/prometheus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/prometheus.png -------------------------------------------------------------------------------- /labs/images/promtheus-select-targets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/promtheus-select-targets.png -------------------------------------------------------------------------------- /labs/images/promtheus-targets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/promtheus-targets.png -------------------------------------------------------------------------------- /labs/images/queries-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/queries-01.png -------------------------------------------------------------------------------- /labs/images/query-engine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/query-engine.png -------------------------------------------------------------------------------- /labs/images/query-enginex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/query-enginex.png -------------------------------------------------------------------------------- /labs/images/thanos-query-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/thanos-query-api.png -------------------------------------------------------------------------------- /labs/images/thanos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/images/thanos.png -------------------------------------------------------------------------------- /labs/lab1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | LABEL maintainer="Niloy Saha " 3 | LABEL description="Rogers Workshop v1.0" 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | 6 | RUN apt-get update \ 7 | && apt-get install iproute2 iputils-ping curl -y \ 8 | && apt-get install vim nano iperf3 net-tools tcpdump -y \ 9 | && apt-get clean 10 | 11 | RUN mkdir -p /workshop/ 12 | WORKDIR /workshop 13 | COPY /app/requirements.txt ./ 14 | RUN pip install -r requirements.txt 15 | COPY /app/* ./ 16 | 17 | EXPOSE 8000 18 | 19 | CMD ["python3", "exporter.py"] -------------------------------------------------------------------------------- /labs/lab1/README.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/lab1/README.pdf -------------------------------------------------------------------------------- /labs/lab1/app/exporter.py: -------------------------------------------------------------------------------- 1 | from prometheus_client import start_http_server, Gauge 2 | import random 3 | import time 4 | 5 | RESPONSE_TIME = Gauge('workshop_response_time_seconds', 'Response time in seconds', ['service', 'region']) 6 | 7 | # Initial response times for each service and region 8 | metric_values = { 9 | ('auth_service', 'us-west'): 0.5, 10 | ('auth_service', 'us-east'): 0.6, 11 | ('payment_service', 'us-west'): 0.8, 12 | ('payment_service', 'us-east'): 0.9, 13 | } 14 | 15 | def adjust_value(current_value, min_value, max_value, fluctuation=0.05): 16 | """Adjust the value slightly within a defined range.""" 17 | change = random.uniform(-fluctuation, fluctuation) # Small fluctuation 18 | new_value = current_value + change 19 | return max(min_value, min(max_value, new_value)) 20 | 21 | def collect_simulated_metrics(): 22 | """Simulate gradual changes in response time for each service and region.""" 23 | for (service, region), response_time in metric_values.items(): 24 | metric_values[(service, region)] = adjust_value(response_time, 0.1, 1.0) 25 | 26 | # add labels to Gauge and set values 27 | RESPONSE_TIME.labels(service=service, region=region).set(metric_values[(service, region)]) 28 | 29 | if __name__ == '__main__': 30 | # Start up the server to expose metrics at http://localhost:8000/metrics 31 | start_http_server(8000) 32 | print("Serving workshop metrics at http://localhost:8000/metrics") 33 | 34 | # Collect and update metrics every 5 seconds 35 | while True: 36 | collect_simulated_metrics() 37 | time.sleep(5) -------------------------------------------------------------------------------- /labs/lab1/app/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | prometheus_client -------------------------------------------------------------------------------- /labs/lab1/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: prom-exporter 5 | namespace: monarch 6 | labels: 7 | app: exporter 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: exporter 12 | replicas: 1 13 | template: 14 | metadata: 15 | labels: 16 | app: exporter 17 | spec: 18 | containers: 19 | - image: ghcr.io/niloysh/rogers-workshop:v1.0 20 | name: exporter 21 | imagePullPolicy: Always 22 | ports: 23 | - name: metrics 24 | containerPort: 8000 25 | command: ["/bin/bash", "-c", "--"] 26 | # args: ["while true; do sleep 30000000; done;"] 27 | args: ["python -u exporter.py"] 28 | resources: 29 | requests: 30 | memory: "100Mi" 31 | cpu: "100m" 32 | limits: 33 | memory: "200Mi" 34 | cpu: "200m" 35 | restartPolicy: Always -------------------------------------------------------------------------------- /labs/lab1/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: prom-exporter-service 5 | namespace: monarch 6 | labels: 7 | app: exporter 8 | annotations: 9 | prometheus.io/scrape: "true" 10 | prometheus.io.scheme: "http" 11 | prometheus.io/path: "/metrics" 12 | prometheus.io/port: "8000" # which port should Prometheus scrape 13 | spec: 14 | ports: 15 | - name: metrics 16 | port: 8000 # exposed port in pod 17 | targetPort: metrics # port name in pod 18 | selector: 19 | app: exporter # target pods -------------------------------------------------------------------------------- /labs/lab2/README.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/lab2/README.pdf -------------------------------------------------------------------------------- /labs/lab3/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | marp: true 3 | theme: default 4 | paginate: true 5 | # size: 4:3 6 | --- 7 | 8 | # Lab 3: Using the Thanos HTTP API with Python 9 | 10 | In this lab, you will learn how to use Thanos's HTTP API using Python. 11 | 12 | Using Python's HTTP requests library, you can programmatically access metrics for more flexible data manipulation and analysis. 13 | 14 | This approach lets you combine, filter, and analyze metrics, which is especially useful for complex metrics like resource utilization across network functions. 15 | 16 | --- 17 | 18 | # Using the Thanos HTTP API with Python (1/3) 19 | 20 | We can query our metrics using Thanos Querier's HTTP API. 21 | 22 | ![](../images/thanos.png) 23 | 24 | --- 25 | # Using the Thanos HTTP API with Python (2/3) 26 | 27 | 1. Open `labs/lab3/exercise.py`: This file contains a skeleton of code for **Tasks 1 through 3**. 28 | 2. Structure of the Exercise Code: 29 | - `query_prometheus`: Sends a Prometheus query to the Thanos HTTP API. 30 | - `parse_numeric_value_from_prometheus_response`: Parses numeric results from the API response. 31 | - `parse_data_from_prometheus_response`: Parses return data (raw) from the API response. 32 | 33 | --- 34 | # Using the Thanos HTTP API with Python (3/3) 35 | 36 | ### Example query in python 37 | 38 | In exercise.py you will find the following snippet, showing an example of using the HTTP API. 39 | 40 | ```python 41 | query = 'fivegs_amffunction_rm_registeredsubnbr' 42 | response = query_prometheus(query) 43 | print(response) 44 | ``` 45 | 46 | **Task 1**: Try running `python3 exercise.py` to see the raw JSON response from the API. 47 | 48 | --- 49 | 50 | # Retrieve VNFM Metrics for Resource Utilization 51 | 52 | 1. **Pod Status and Resource Utilization**: VNFM metrics can provide insights into the health of 5G network functions by monitoring the status of Kubernetes pods and resource utilization. 53 | 54 | 2. **Query Example**: Query metrics like `kube_pod_info` and `kube_pod_container_resource_requests` to get details about the network functions' CPU and memory usage. 55 | 56 | **Task 2**: Modify the code to retrieve CPU and memory usage data, then parse and display it in a readable format. 57 | 58 | --- 59 | # Compose Slice-Level KPIs (1/2) 60 | 61 | 62 | We've already explored examples of slice-level metrics, like `slice_throughput`, as we saw in our Grafana dashboard. 63 | 64 | Monarch's KPI computation module leverages the Python HTTP API to query raw metrics, calculate KPIs at the slice level, and store them back in a `data_store`, making them accessible for visualization in Grafana. 65 | 66 | 67 | 68 | **Task 3**: 69 | - **Task 3** of `labs/lab3/exercise.py` dives deeper into composing slice-level metrics, specifically focusing on calculating resource utilization for slice 1 (with `SNSSAI=1-000001`). 70 | - You'll combine multiple queries to determine memory usage, providing a practical understanding of how to build KPIs from various metric sources. 71 | 72 | --- 73 | # Compose Slice-Level KPIs (2/2) 74 | 75 | **Step 1: Retrieve Memory Requests** 76 | - Query kube_pod_container_resource_requests to get memory requests for SMF1 and UPF1. Parse and store these values. 77 | 78 | **Step 2: Retrieve Memory Usage** 79 | - Use `avg_over_time(container_memory_working_set_bytes{pod=~".*smf1.*"}[5m])` to find the 5-minute average memory usage for SMF1. Do the same for UPF1. 80 | 81 | **Step 3: Calculate the Percentage** 82 | - Sum the memory requests and usage for both SMF1 and UPF1. Calculate the percentage of requested memory currently being used. 83 | 84 | --- 85 | # Next Steps 86 | 87 | **Congratulations!** 88 | You've successfully completed the following: 89 | - Learned how to use the Thanos HTTP API using Python. 90 | - Learned how to use multiple queries to compute different 5G related KPIs. 91 | 92 | **You have completed Day 1 of our workshop. What's Next?** 93 | 94 | 95 | ![](../images/beyond-monarch.png) 96 | 97 | 98 | -------------------------------------------------------------------------------- /labs/lab3/README.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/labs/lab3/README.pdf -------------------------------------------------------------------------------- /labs/lab3/exercise.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import numpy as np 3 | import logging 4 | import os 5 | from dotenv import load_dotenv 6 | 7 | # Logging configuration 8 | log = logging.getLogger(__name__) 9 | log.setLevel(logging.INFO) 10 | log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 11 | console_handler = logging.StreamHandler() 12 | console_handler.setFormatter(log_format) 13 | log.addHandler(console_handler) 14 | 15 | # Load environment variables 16 | load_dotenv() 17 | MONARCH_THANOS_URL = os.getenv("MONARCH_THANOS_URL") 18 | 19 | 20 | def parse_numeric_value_from_prometheus_response(response_json): 21 | """ 22 | Parse a numeric value from the Prometheus response JSON. 23 | """ 24 | try: 25 | results = response_json["data"]["result"] 26 | values = [float(result["value"][1]) for result in results] 27 | log.debug(values) 28 | if values: 29 | value = np.sum(values) 30 | return value 31 | else: 32 | return None 33 | except (KeyError, IndexError, ValueError) as e: 34 | log.error(f"Failed to parse Prometheus response: {e}") 35 | 36 | def parse_data_from_prometheus_response(response_json): 37 | """ 38 | Parse the labels from the Prometheus response JSON. 39 | """ 40 | try: 41 | labels = response_json["data"]["result"][0]["metric"] 42 | return labels 43 | except (KeyError, IndexError, ValueError) as e: 44 | log.error(f"Failed to parse Prometheus response: {e}") 45 | 46 | def query_prometheus(query, url=MONARCH_THANOS_URL): 47 | """ 48 | Query Prometheus using requests and return value. 49 | """ 50 | try: 51 | params = {'query': query} 52 | log.debug(params["query"]) 53 | response = requests.get(url + '/api/v1/query', params) 54 | if response.status_code == 200: 55 | return response.json() 56 | else: 57 | log.error(f"Error: {response.status_code}") 58 | return None 59 | 60 | except requests.exceptions.RequestException as e: 61 | log.error(f"Failed to query Prometheus: {e}") 62 | 63 | 64 | if __name__ == "__main__": 65 | 66 | ####################### TASK 1: Using the HTTP API with Python ####################### 67 | 68 | # Task 1a: Query a sample metric from Prometheus. 69 | # Here, we query for the number of registered subscribers in the AMF function. 70 | query = 'fivegs_amffunction_rm_registeredsubnbr' 71 | response = query_prometheus(query) 72 | print("Registered subscribers:", response) 73 | 74 | # Task 1b: Count the number of pods in the "open5gs" namespace. 75 | # Step 1: Write the appropriate query. 76 | # Hint: Use the `count()` function with `kube_pod_info` and filter by namespace. 77 | query = 'count(kube_pod_info{namespace="open5gs"})' 78 | result = query_prometheus(query, MONARCH_THANOS_URL) 79 | result = parse_numeric_value_from_prometheus_response(result) 80 | print(f"Number of pods in 'open5gs' namespace: {result}") 81 | 82 | ####################### TASK 2: Retrieve VNFM Metrics ########################### 83 | 84 | # Task 2a: Identify the node where "UPF1" pod is deployed. 85 | # Write a query to retrieve the node information for the pod "UPF1". 86 | # Uncomment and complete the code below. 87 | # Hint: Use `kube_pod_info` and filter with a regex: `{pod=~".*upf1.*"}` 88 | # 89 | # query = '' # Your query here 90 | # result = query_prometheus(query) 91 | # node_name = parse_data_from_prometheus_response(result).get("node") 92 | # print(f"UPF1 is deployed on node: {node_name}") 93 | 94 | # Task 2b: Check the CPU limit set for the "UPF1" pod. 95 | # Write a query to get the CPU limits for the "UPF1" pod. 96 | # Uncomment and complete the code below. 97 | # Hint: Use `kube_pod_container_resource_limits`. 98 | # 99 | # query = '' # Your query here 100 | # result = query_prometheus(query) 101 | # result = parse_numeric_value_from_prometheus_response(result) 102 | # print(f"The CPU limit for 'UPF1' pod is {result * 1000} millicores") 103 | 104 | # Task 2c: Find the memory request for the "AMF" pod. 105 | # Write a query to get the memory request limits for the "AMF" pod. 106 | # Uncomment and complete the code below. 107 | # Hint: Use `kube_pod_container_resource_limits`. 108 | # 109 | # query = '' # Your query here 110 | # result = query_prometheus(query) 111 | # result = parse_numeric_value_from_prometheus_response(result) 112 | # print(f"The memory request for 'AMF' pod is {result / 1000000} MB") 113 | 114 | # Task 2d: Measure the CPU usage of the "UPF1" pod. 115 | # This query has been pre-filled for you. Just uncomment the code below and run it. 116 | # 117 | # query = 'rate(container_cpu_usage_seconds_total{pod=~".*upf1.*", container!=""}[1m])' 118 | # result = query_prometheus(query, MONARCH_THANOS_URL) 119 | # result = parse_numeric_value_from_prometheus_response(result) 120 | # print(f"Current CPU usage for 'UPF1' pod is {result * 1000} millicores") 121 | 122 | ################ TASK 3: Compose Slice-Level KPIs ############################## 123 | 124 | # Task 3: Calculate the memory usage percentage for Slice 1 (SNSSAI=1-000001). 125 | # Step 1: Retrieve the memory requests and usage for SMF1 and UPF1. 126 | # Use appropriate queries and hints provided to complete each step below. 127 | 128 | # Uncomment and replace `''` with actual queries. 129 | 130 | # Step 1a: Query memory requests for SMF1 131 | # query = '' # Your query here (memory requests for SMF1) 132 | # smf1_requested_memory = query_prometheus(query, MONARCH_THANOS_URL) 133 | # smf1_requested_memory = parse_numeric_value_from_prometheus_response(smf1_requested_memory) 134 | 135 | # Step 1b: Query memory usage for SMF1 (this part is pre-filled) 136 | # query = 'avg_over_time(container_memory_working_set_bytes{pod=~".*smf1.*"}[5m])' 137 | # smf1_memory_usage = query_prometheus(query, MONARCH_THANOS_URL) 138 | # smf1_memory_usage = parse_numeric_value_from_prometheus_response(smf1_memory_usage) 139 | 140 | # Step 2a: Query memory requests for UPF1 141 | # query = '' # Your query here (memory requests for UPF1) 142 | # upf1_requested_memory = query_prometheus(query, MONARCH_THANOS_URL) 143 | # upf1_requested_memory = parse_numeric_value_from_prometheus_response(upf1_requested_memory) 144 | 145 | # Step 2b: Query memory usage for UPF1 146 | # query = '' # Your query here (memory usage for UPF1) 147 | # upf1_memory_usage = query_prometheus(query, MONARCH_THANOS_URL) 148 | # upf1_memory_usage = parse_numeric_value_from_prometheus_response(upf1_memory_usage) 149 | 150 | # Step 3: Calculate the percentage of requested memory being used. 151 | # Uncomment and complete the calculations below. 152 | # 153 | # total_requested_memory = smf1_requested_memory + upf1_requested_memory 154 | # total_memory_usage = smf1_memory_usage + upf1_memory_usage 155 | # percent_memory_used = (total_memory_usage / total_requested_memory) * 100 156 | # print(f"Percentage of requested memory being used by Slice 1 (SMF1 and UPF1): {percent_memory_used:.2f}%") -------------------------------------------------------------------------------- /mde/README.md: -------------------------------------------------------------------------------- 1 | # MDEs 2 | MDEs allow the NSSDC to collect data from the Network Function (NF) by exposing metrics (such as gtp packets, number of sessions, etc.) via an HTTP endpoint. 3 | 4 | There are two options for setting up the monitoring service: `standard` and `otel` (OpenTelemetry). 5 | 6 | The `standard` option uses ServiceMonitors, which are lightweight Kubernetes objects that define the scraping targets for Prometheus. A ServiceMonitor identifies these endpoints and instructs Prometheus to scrape metrics from them. ServiceMonitors themselves do not consume significant resources. 7 | 8 | The `otel` option uses the OpenTelemetry Collector, which is a vendor-agnostic implementation that can be used as a metrics (and traces) pipeline. 9 | This option is useful if we want to do additional processing or export the metrics to multiple monitoring services. 10 | 11 | To install the service, run the `install.sh` script with either `standard` or `otel` as the argument, like so: 12 | 13 | ```bash 14 | ./install.sh standard 15 | ``` -------------------------------------------------------------------------------- /mde/check-mde.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | kubectl get svc -n open5gs -l app=monarch -o json | jq .items[].metadata.name -------------------------------------------------------------------------------- /mde/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NAMESPACE="monarch" 3 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | cd "$SCRIPT_DIR" 5 | kubectl get namespace $NAMESPACE 2>/dev/null || kubectl create namespace $NAMESPACE 6 | set -o allexport; source ../.env; set +o allexport 7 | kubectl apply -f metrics-service.yaml 8 | # envsubst < standard/metrics-servicemonitor.yaml | kubectl apply -f - 9 | -------------------------------------------------------------------------------- /mde/metrics-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: amf-metrics-service 5 | namespace: open5gs 6 | labels: 7 | nf: amf 8 | app: monarch 9 | annotations: 10 | prometheus.io/scrape: "true" 11 | prometheus.io.scheme: "http" 12 | prometheus.io/path: "/metrics" 13 | prometheus.io/port: "9090" 14 | spec: 15 | ports: 16 | - name: metrics # expose metrics port 17 | port: 9090 # defined in amf chart 18 | selector: 19 | nf: amf # target amf pods 20 | --- 21 | apiVersion: v1 22 | kind: Service 23 | metadata: 24 | name: smf-metrics-service 25 | namespace: open5gs 26 | labels: 27 | nf: smf 28 | app: monarch 29 | annotations: 30 | prometheus.io/scrape: "true" 31 | prometheus.io.scheme: "http" 32 | prometheus.io/path: "/metrics" 33 | prometheus.io/port: "9090" 34 | spec: 35 | clusterIP: None 36 | ports: 37 | - name: metrics # expose metrics port 38 | port: 9090 # defined in smf chart 39 | selector: 40 | nf: smf # target smf pods 41 | --- 42 | apiVersion: v1 43 | kind: Service 44 | metadata: 45 | name: upf-metrics-service 46 | namespace: open5gs 47 | labels: 48 | nf: upf 49 | app: monarch 50 | annotations: 51 | prometheus.io/scrape: "true" 52 | prometheus.io.scheme: "http" 53 | prometheus.io/path: "/metrics" 54 | prometheus.io/port: "9090" 55 | spec: 56 | clusterIP: None 57 | ports: 58 | - name: metrics # expose metrics port 59 | port: 9090 # defined in upf chart 60 | selector: 61 | nf: upf # target upf pods 62 | -------------------------------------------------------------------------------- /mde/otel/collector.yaml: -------------------------------------------------------------------------------- 1 | receivers: 2 | prometheus: 3 | config: 4 | scrape_configs: 5 | - job_name: smf-collector 6 | scrape_interval: 1s 7 | static_configs: 8 | - targets: [smf-metrics-service.open5gs.svc.cluster.local:9090] 9 | 10 | - job_name: upf-collector 11 | scrape_interval: 1s 12 | static_configs: 13 | - targets: [upf-metrics-service.open5gs.svc.cluster.local:9090] 14 | 15 | processors: 16 | exporters: 17 | prometheus: 18 | endpoint: 0.0.0.0:8889 19 | namespace: monarch 20 | logging: 21 | 22 | service: 23 | pipelines: 24 | metrics: 25 | receivers: [prometheus] 26 | processors: [] 27 | exporters: [prometheus] 28 | -------------------------------------------------------------------------------- /mde/otel/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | namespace: monarch 5 | 6 | resources: 7 | - otel-deployment.yaml 8 | - otel-service.yaml 9 | - otel-servicemonitor.yaml 10 | 11 | configMapGenerator: 12 | - name: otel-configmap 13 | behavior: create 14 | files: 15 | - collector.yaml 16 | options: 17 | disableNameSuffixHash: true 18 | -------------------------------------------------------------------------------- /mde/otel/otel-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mde-otel-collector 5 | labels: 6 | app: monarch 7 | component: mde 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: monarch 12 | component: mde 13 | replicas: 1 14 | template: 15 | metadata: 16 | labels: 17 | app: monarch 18 | component: mde 19 | spec: 20 | containers: 21 | - image: otel/opentelemetry-collector-contrib:0.94.0 22 | imagePullPolicy: IfNotPresent 23 | name: collector 24 | ports: 25 | - name: metrics 26 | containerPort: 8889 27 | # command: ["./open5gs-amfd"] 28 | args: [--config=/conf/collector.yaml] 29 | env: 30 | - name: GIN_MODE 31 | value: release 32 | volumeMounts: 33 | - mountPath: /conf 34 | name: otel-volume 35 | resources: 36 | requests: 37 | memory: "100Mi" 38 | cpu: "100m" 39 | limits: 40 | memory: "200Mi" 41 | cpu: "200m" 42 | dnsPolicy: ClusterFirst 43 | restartPolicy: Always 44 | volumes: 45 | - name: otel-volume 46 | configMap: 47 | name: otel-configmap 48 | items: 49 | - key: collector.yaml 50 | path: collector.yaml 51 | -------------------------------------------------------------------------------- /mde/otel/otel-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: mde-otel-service 5 | labels: 6 | app: monarch 7 | component: mde 8 | spec: 9 | ports: 10 | - name: metrics # expose metrics port 11 | port: 8889 # defined in deployment 12 | selector: 13 | app: monarch # target pods 14 | component: mde 15 | -------------------------------------------------------------------------------- /mde/otel/otel-servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: ServiceMonitor 3 | metadata: 4 | name: mde-otel-servicemonitor 5 | namespace: monarch 6 | labels: 7 | app: monarch 8 | component: mde 9 | spec: 10 | namespaceSelector: 11 | any: true # important otherwise this is not picked up 12 | selector: 13 | matchLabels: 14 | app: monarch # target service 15 | component: mde 16 | endpoints: 17 | - port: metrics 18 | interval: 1s 19 | -------------------------------------------------------------------------------- /mde/standard/metrics-servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: ServiceMonitor 3 | metadata: 4 | name: amf-servicemonitor 5 | namespace: monarch 6 | labels: 7 | nf: amf 8 | app: monarch 9 | spec: 10 | namespaceSelector: 11 | any: true # important otherwise this is not picked up 12 | selector: 13 | matchLabels: 14 | nf: amf # target amf service 15 | endpoints: 16 | - port: metrics 17 | interval: "${MONARCH_MONITORING_INTERVAL}" 18 | --- 19 | apiVersion: monitoring.coreos.com/v1 20 | kind: ServiceMonitor 21 | metadata: 22 | name: smf-servicemonitor 23 | namespace: monarch 24 | labels: 25 | nf: smf 26 | app: monarch 27 | spec: 28 | namespaceSelector: 29 | any: true # important otherwise this is not picked up 30 | selector: 31 | matchLabels: 32 | nf: smf # target smf service 33 | endpoints: 34 | - port: metrics 35 | interval: "${MONARCH_MONITORING_INTERVAL}" 36 | --- 37 | apiVersion: monitoring.coreos.com/v1 38 | kind: ServiceMonitor 39 | metadata: 40 | name: upf-servicemonitor 41 | namespace: monarch 42 | labels: 43 | nf: upf 44 | app: monarch 45 | spec: 46 | namespaceSelector: 47 | any: true # important otherwise this is not picked up 48 | selector: 49 | matchLabels: 50 | nf: upf # target upf service 51 | endpoints: 52 | - port: metrics 53 | interval: "${MONARCH_MONITORING_INTERVAL}" 54 | -------------------------------------------------------------------------------- /mde/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NAMESPACE="monarch" 3 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | cd "$SCRIPT_DIR" 5 | # kubectl delete -f standard/metrics-servicemonitor.yaml 6 | kubectl delete -f metrics-service.yaml -------------------------------------------------------------------------------- /monitoring_manager/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | LABEL maintainer="Niloy Saha " 3 | LABEL description="Monitoring Manager v1.0.0 for Monarch" 4 | 5 | 6 | RUN mkdir -p /monarch/ 7 | 8 | WORKDIR /monarch 9 | COPY /app/requirements.txt ./ 10 | RUN apt-get update \ 11 | && apt-get install -y --no-install-recommends \ 12 | vim iputils-ping curl \ 13 | && apt-get autoremove -y && apt-get autoclean 14 | RUN pip install -r requirements.txt 15 | 16 | # Install kubectl version 1.28.2 17 | RUN curl -LO "https://storage.googleapis.com/kubernetes-release/release/v1.28.2/bin/linux/amd64/kubectl" \ 18 | && chmod +x ./kubectl \ 19 | && mv ./kubectl /usr/local/bin/kubectl 20 | 21 | COPY app /monarch/app 22 | COPY run.py ./ 23 | 24 | 25 | EXPOSE 5000 26 | 27 | CMD python3 run.py -------------------------------------------------------------------------------- /monitoring_manager/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/monitoring_manager/README.md -------------------------------------------------------------------------------- /monitoring_manager/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/monitoring_manager/app/__init__.py -------------------------------------------------------------------------------- /monitoring_manager/app/directive_manager.py: -------------------------------------------------------------------------------- 1 | from app.logger import setup_logger 2 | from app.orchestrator import NFVOrchestratorManager 3 | import requests 4 | from requests.models import Response 5 | 6 | 7 | class DirectiveManager: 8 | def __init__(self, nfv_orchestrator: NFVOrchestratorManager): 9 | self.logger = setup_logger("directive_manager") 10 | self.nfv_orchestrator = nfv_orchestrator 11 | 12 | def process_directive(self, directive): 13 | self.logger.info("Processing directive: %s", directive) 14 | kpi_name = directive["kpi_name"] 15 | if kpi_name == "slice_throughput": 16 | return self.process_slice_throughput_directive(directive) 17 | 18 | else: 19 | self.logger.error(f"KPI {kpi_name} not supported") 20 | raise NotImplementedError(f"KPI {kpi_name} not supported") 21 | 22 | def process_slice_throughput_directive(self, directive): 23 | self.logger.info("Processing slice throughput directive: %s", directive) 24 | 25 | if directive["action"] == "create": 26 | # for now, we will just install pre-configured MDE and KPI Computation 27 | # if NFV orchestrator supports it, we can change the configuration MDE and KPI computation components 28 | # using the information in the directive 29 | self.logger.info("Installing MDE") 30 | response_mde = self.nfv_orchestrator.mde_install() 31 | if response_mde.status_code != 200: 32 | self.logger.error("Error installing MDE: %s", response_mde.text) 33 | return response_mde 34 | 35 | self.logger.info("Installing KPI Computation") 36 | response_kpi = self.nfv_orchestrator.kpi_computation_install() 37 | if response_kpi.status_code != 200: 38 | self.logger.error("Error installing KPI Computation: %s", response_kpi.text) 39 | return response_kpi 40 | 41 | self.logger.info("Both MDE and KPI Computation installed successfully.") 42 | return self._create_success_response(action="installed") 43 | 44 | elif directive["action"] == "delete": 45 | self.logger.info("Uninstalling MDE") 46 | response_mde = self.nfv_orchestrator.mde_uninstall() 47 | if response_mde.status_code != 200: 48 | self.logger.error("Error uninstalling MDE: %s", response_mde.text) 49 | return response_mde 50 | 51 | self.logger.info("Uninstalling KPI Computation") 52 | response_kpi = self.nfv_orchestrator.kpi_computation_uninstall() 53 | if response_kpi.status_code != 200: 54 | self.logger.error("Error uninstalling KPI Computation: %s", response_kpi.text) 55 | return response_kpi 56 | 57 | self.logger.info("Both MDE and KPI Computation uninstalled successfully.") 58 | return self._create_success_response(action="deleted") 59 | 60 | def _create_success_response(self, action="installed"): 61 | response = Response() 62 | response.status_code = 200 63 | response._content = f"Both MDE and KPI Computation {action} successfully.".encode("utf-8") 64 | response.encoding = "utf-8" 65 | return response 66 | -------------------------------------------------------------------------------- /monitoring_manager/app/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | def setup_logger(name): 5 | logger = logging.getLogger(name) 6 | logger.setLevel(logging.INFO) 7 | 8 | ch = logging.StreamHandler() 9 | ch.setLevel(logging.INFO) 10 | 11 | formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") 12 | ch.setFormatter(formatter) 13 | 14 | if not logger.hasHandlers(): 15 | logger.addHandler(ch) 16 | 17 | return logger 18 | -------------------------------------------------------------------------------- /monitoring_manager/app/monitoring_manager.py: -------------------------------------------------------------------------------- 1 | from app.logger import setup_logger 2 | from app.orchestrator import NFVOrchestratorManager 3 | from app.directive_manager import DirectiveManager 4 | from flask import Flask, request, jsonify 5 | 6 | 7 | class MonitoringManager: 8 | def __init__(self, nfv_orchestrator_uri): 9 | self.logger = setup_logger("monitoring_manager") 10 | self.app = Flask(__name__) 11 | self.directives = [] 12 | self.nfv_orchestrator = NFVOrchestratorManager(nfv_orchestrator_uri) 13 | self.directive_manager = DirectiveManager(self.nfv_orchestrator) 14 | self._set_routes() 15 | 16 | def _set_routes(self): 17 | self.app.add_url_rule( 18 | "/api/monitoring-directives", 19 | "receive_directive", 20 | self.receive_directive, 21 | methods=["POST"], 22 | ) 23 | self.app.add_url_rule( 24 | "/api/monitoring-directives/delete", 25 | "delete_directive", 26 | self.delete_directive, 27 | methods=["POST"], 28 | ) 29 | self.app.add_url_rule( 30 | "/api/monitoring-directives", 31 | "list_directives", 32 | self.list_directives, 33 | methods=["GET"], 34 | ) 35 | self.app.add_url_rule( 36 | "/api/health", 37 | "health_check", 38 | self.health_check, 39 | methods=["GET"], 40 | ) 41 | 42 | def receive_directive(self): 43 | data = request.get_json() 44 | self.logger.info("Received directive: %s", data) 45 | self.directives.append(data) 46 | response = self.directive_manager.process_directive(data) 47 | return jsonify({"status": "success", "message": response.text}), response.status_code 48 | 49 | def health_check(self): 50 | return jsonify({"status": "success", "message": "Monitoring Manager is healthy"}), 200 51 | 52 | def delete_directive(self): 53 | data = request.get_json() 54 | self.logger.info("Received delete directive: %s", data) 55 | for directive in self.directives: 56 | if directive["request_id"] == data["request_id"]: 57 | self.directives.remove(directive) 58 | response = self.directive_manager.process_directive(data) 59 | return jsonify({"status": "success", "message": response.text}), response.status_code 60 | return jsonify({"status": "error", "message": "Directive not found"}), 404 61 | 62 | def list_directives(self): 63 | return jsonify(self.directives), 200 64 | 65 | def run(self, debug=False, port=5000, host="0.0.0.0"): 66 | self.app.run(debug=debug, port=port, host=host) 67 | -------------------------------------------------------------------------------- /monitoring_manager/app/orchestrator.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | from app.logger import setup_logger 4 | 5 | 6 | class NFVOrchestratorManager: 7 | def __init__(self, nfv_orchestrator_uri): 8 | self.logger = setup_logger("nfv_orchestrator") 9 | self.nfv_orchestrator_uri = nfv_orchestrator_uri 10 | self.connect_to_nfv_orchestrator() 11 | 12 | def is_nfv_orchestrator_available(self): 13 | try: 14 | response = requests.get(self.nfv_orchestrator_uri + "/api/health") 15 | return response.status_code == 200 16 | except requests.exceptions.RequestException: 17 | return False 18 | 19 | def connect_to_nfv_orchestrator(self, max_retries=5, wait_time=5): 20 | attempt = 0 21 | while attempt < max_retries: 22 | self.logger.info(f"Attempt {attempt + 1} to connect to NFV Orchestrator at {self.nfv_orchestrator_uri}") 23 | if self.is_nfv_orchestrator_available(): 24 | self.logger.info("Successfully connected to NFV Orchestrator") 25 | return True 26 | attempt += 1 27 | time.sleep(wait_time) 28 | self.logger.error(f"Could not connect to NFV Orchestrator after {max_retries} attempts") 29 | exit(1) 30 | 31 | def mde_install(self): 32 | response = requests.post(self.nfv_orchestrator_uri + "/mde/install") 33 | return response 34 | 35 | def mde_uninstall(self): 36 | response = requests.post(self.nfv_orchestrator_uri + "/mde/uninstall") 37 | return response 38 | 39 | def kpi_computation_install(self): 40 | response = requests.post(self.nfv_orchestrator_uri + "/kpi-computation/install") 41 | return response 42 | 43 | def kpi_computation_uninstall(self): 44 | response = requests.post(self.nfv_orchestrator_uri + "/kpi-computation/uninstall") 45 | return response 46 | -------------------------------------------------------------------------------- /monitoring_manager/app/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | python-dotenv 3 | pymongo 4 | flask 5 | kubernetes 6 | jsonschema 7 | shortuuid -------------------------------------------------------------------------------- /monitoring_manager/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NAMESPACE="monarch" 3 | MODULE_NAME="monitoring-manager" 4 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | cd "$SCRIPT_DIR" 6 | set -o allexport; source ../.env; set +o allexport 7 | kubectl get namespace $NAMESPACE 2>/dev/null || kubectl create namespace $NAMESPACE 8 | 9 | # Check if NFV_ORCHESTRATOR_URI is set 10 | if [ -z "$NFV_ORCHESTRATOR_URI" ]; then 11 | echo "Error: NFV_ORCHESTRATOR_URI is not set in .env file." 12 | exit 1 13 | fi 14 | 15 | envsubst < manifests/deployment.yaml | kubectl apply -f - 16 | envsubst < manifests/service.yaml | kubectl apply -f - 17 | 18 | -------------------------------------------------------------------------------- /monitoring_manager/manifests/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: monitoring-manager 5 | namespace: monarch 6 | labels: 7 | app: monarch 8 | component: monitoring-manager 9 | spec: 10 | selector: 11 | matchLabels: 12 | app: monarch 13 | component: monitoring-manager 14 | replicas: 1 15 | template: 16 | metadata: 17 | labels: 18 | app: monarch 19 | component: monitoring-manager 20 | spec: 21 | # nodeSelector: 22 | # kubernetes.io/hostname: nuc1 23 | containers: 24 | - image: ghcr.io/niloysh/monitoring-manager:v1.0.0 25 | name: monitoring-manager 26 | imagePullPolicy: Always 27 | ports: 28 | - name: api 29 | containerPort: 6000 30 | command: ["/bin/bash", "-c", "--"] 31 | args: ["python -u run.py"] 32 | # args: ["while true; do sleep 30000000; done;"] 33 | env: 34 | - name: MONARCH_MONGO_URI 35 | value: mongodb://datastore-mongodb.monarch.svc.cluster.local:27017 36 | - name: NFV_ORCHESTRATOR_URI 37 | value: ${NFV_ORCHESTRATOR_URI} 38 | resources: 39 | requests: 40 | memory: "100Mi" 41 | cpu: "100m" 42 | limits: 43 | memory: "200Mi" 44 | cpu: "200m" 45 | readinessProbe: 46 | httpGet: 47 | path: /api/health 48 | port: 6000 49 | initialDelaySeconds: 5 50 | periodSeconds: 10 51 | timeoutSeconds: 2 52 | successThreshold: 1 53 | failureThreshold: 3 54 | restartPolicy: Always 55 | -------------------------------------------------------------------------------- /monitoring_manager/manifests/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: monitoring-manager-service 5 | namespace: monarch 6 | labels: 7 | app: monarch 8 | component: monitoring-manager 9 | spec: 10 | type: NodePort 11 | ports: 12 | - name: api # expose server port 13 | port: 6000 # which port is exposed 14 | targetPort: api # port name in pod 15 | nodePort: 30600 16 | selector: 17 | app: monarch # target pods 18 | component: monitoring-manager 19 | -------------------------------------------------------------------------------- /monitoring_manager/run.py: -------------------------------------------------------------------------------- 1 | from app.monitoring_manager import MonitoringManager 2 | from app.logger import setup_logger 3 | import os 4 | from dotenv import load_dotenv 5 | 6 | load_dotenv() 7 | NFV_ORCHESTRATOR_URI = os.getenv("NFV_ORCHESTRATOR_URI", "http://localhost:6001") 8 | 9 | 10 | def main(): 11 | logger = setup_logger("app") 12 | logger.info("Starting Monitoring Manager service") 13 | 14 | monitoring_manager = MonitoringManager(NFV_ORCHESTRATOR_URI) 15 | monitoring_manager.run(debug=True, port=6000, host="0.0.0.0") 16 | 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /monitoring_manager/test-monitoring-manager.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | print_subheader() { 4 | echo -e "\e[1;36m--- $1 ---\e[0m" 5 | } 6 | 7 | print_header() { 8 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 9 | } 10 | 11 | print_success() { 12 | echo -e "\e[1;32m$1\e[0m" 13 | } 14 | 15 | print_error() { 16 | echo -e "\e[1;31mERROR: $1\e[0m" 17 | } 18 | 19 | print_info() { 20 | echo -e "\e[1;33mINFO: $1\e[0m" 21 | } 22 | 23 | 24 | 25 | print_header "Testing Monitoring Manager (Monarch Core [4/5])" 26 | 27 | print_subheader "Performing health check for Monitoring Manager" 28 | response=$(curl -s -w "%{http_code}" -o /dev/null http://localhost:30600/api/health) 29 | 30 | if [ "$response" -eq 200 ]; then 31 | print_success "Health check passed! Monitoring Manager is healthy." 32 | else 33 | print_error "Health check failed with status code: $response. Please check the Monitoring Manager logs for details." 34 | fi 35 | 36 | # Get the list of monitoring directives 37 | print_subheader "Fetching the list of monitoring directives" 38 | response=$(curl -s -w "%{http_code}" -o /dev/null http://localhost:30600/api/monitoring-directives) 39 | 40 | # Check if the list of directives was successfully retrieved 41 | if [ "$response" -eq 200 ]; then 42 | print_success "Successfully retrieved the list of monitoring directives." 43 | 44 | # Fetch the actual directives list content 45 | directives=$(curl -s http://localhost:30600/api/monitoring-directives) 46 | 47 | # Check if the list is empty 48 | if [ -z "$directives" ] || [ "$directives" == "[]" ]; then 49 | print_info "The list of monitoring directives is empty. No directives found." 50 | else 51 | echo "List of monitoring directives retrieved successfully." 52 | echo "$directives" 53 | fi 54 | else 55 | print_error "Failed to fetch the list of monitoring directives. Status code: $response." 56 | fi -------------------------------------------------------------------------------- /monitoring_manager/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NAMESPACE="monarch" 3 | MODULE_NAME="monitoring-manager" 4 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | cd "$SCRIPT_DIR" 6 | set -o allexport; source ../.env; set +o allexport 7 | kubectl get namespace $NAMESPACE 2>/dev/null || kubectl create namespace $NAMESPACE 8 | envsubst < manifests/deployment.yaml | kubectl delete -f - 9 | envsubst < manifests/service.yaml | kubectl delete -f - 10 | -------------------------------------------------------------------------------- /nfv_orchestrator/README.md: -------------------------------------------------------------------------------- 1 | Dummy NFV Orchestrator 2 | ====================== 3 | This script provides a simple NFV (Network Function Virtualization) Orchestrator using Flask. 4 | It uses the Kubernetes API to install and uninstall MDE and KPI Computation components. 5 | 6 | Overview 7 | -------- 8 | - Intended to be run on a Kubernetes control plane node to avoid loading kubeconfig. 9 | - The monitoring manager component can make HTTP requests to this orchestrator to manage the lifecycle of MDE and KPI Computation components. 10 | - Real NFV Orchestrators would have more complex logic and additional APIs. -------------------------------------------------------------------------------- /nfv_orchestrator/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | SERVICE_NAME="nfv_orchestrator" 5 | USER="$(whoami)" 6 | WORKING_DIR="$(pwd)" 7 | SCRIPT_NAME="nfv-orchestrator.py" 8 | SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" 9 | PYTHON_PATH="$(which python3)" 10 | 11 | # Check if Python is installed 12 | if [ -z "$PYTHON_PATH" ]; then 13 | echo "Python3 is not installed. Please install it and try again." 14 | exit 1 15 | fi 16 | 17 | # Create the service file 18 | echo "Creating systemd service file at ${SERVICE_FILE}..." 19 | 20 | sudo bash -c "cat > ${SERVICE_FILE}" < /dev/null 36 | 37 | # Verify removal 38 | if ! systemctl status "$SERVICE_NAME" &> /dev/null; then 39 | echo "${SERVICE_NAME} service has been successfully removed." 40 | else 41 | echo "Warning: ${SERVICE_NAME} service may still be present." 42 | fi -------------------------------------------------------------------------------- /nfv_orchestrator/view-logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo journalctl -u nfv_orchestrator -f -------------------------------------------------------------------------------- /nssdc/README.md: -------------------------------------------------------------------------------- 1 | # nssdc 2 | 3 | ## Installation 4 | 5 | Create a `thanos-objstore-config.yaml` file in the `nssdc` directory with the following content: 6 | 7 | ```yaml 8 | type: s3 9 | config: 10 | bucket: monarch-thanos 11 | endpoint: ":" 12 | access_key: 13 | secret_key: 14 | insecure: true 15 | ``` 16 | -------------------------------------------------------------------------------- /nssdc/additional-scrape-configs.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/nssdc/additional-scrape-configs.yaml -------------------------------------------------------------------------------- /nssdc/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | KUBE_PROMETHEUS_STACK_VER="51.9.4" 3 | HELM_REPO_URL="https://prometheus-community.github.io/helm-charts" 4 | HELM_REPO_NAME="prometheus-community" 5 | NAMESPACE="monarch" 6 | 7 | kubectl get namespace $NAMESPACE 2>/dev/null || kubectl create namespace $NAMESPACE 8 | helm repo add $HELM_REPO_NAME $HELM_REPO_URL || echo "Helm repo $HELM_REPO_NAME already exists." 9 | helm repo update 10 | 11 | set -o allexport; source ../.env; set +o allexport 12 | kubectl -n $NAMESPACE delete secret thanos-objstore-config --ignore-not-found 13 | kubectl -n $NAMESPACE create secret generic thanos-objstore-config --from-file=thanos.yaml=<(envsubst < thanos-objstore-config.yaml) 14 | 15 | kubectl -n $NAMESPACE delete secret additional-scrape-configs --ignore-not-found 16 | kubectl -n $NAMESPACE create secret generic additional-scrape-configs --from-file=additional-scrape-configs.yaml=<(envsubst < additional-scrape-configs.yaml) 17 | 18 | helm upgrade --install nssdc $HELM_REPO_NAME/kube-prometheus-stack \ 19 | --namespace $NAMESPACE \ 20 | --version $KUBE_PROMETHEUS_STACK_VER \ 21 | --values <(envsubst < values.yaml) 22 | 23 | 24 | -------------------------------------------------------------------------------- /nssdc/status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source ../utils/check_status.sh 3 | check_status "nssdc" "monarch" 4 | 5 | 6 | -------------------------------------------------------------------------------- /nssdc/test-nssdc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | print_subheader() { 4 | echo -e "\e[1;36m--- $1 ---\e[0m" 5 | } 6 | 7 | print_header() { 8 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 9 | } 10 | 11 | print_success() { 12 | echo -e "\e[1;32m$1\e[0m" 13 | } 14 | 15 | print_error() { 16 | echo -e "\e[1;31mERROR: $1\e[0m" 17 | } 18 | 19 | print_info() { 20 | echo -e "\e[1;33mINFO: $1\e[0m" 21 | } 22 | 23 | 24 | print_header "Testing NSSDC (Monarch NSS [1/1])" 25 | print_subheader "Performing health check for NSSDC" 26 | response=$(curl -s -w "%{http_code}" -o /dev/null http://localhost:30095/-/healthy) 27 | 28 | if [ "$response" -eq 200 ]; then 29 | print_success "Health check passed! NSSDC (Prometheus) is healthy." 30 | else 31 | print_error "Health check failed with status code: $response. Please check the NSSDC (Prometheus) logs for details." 32 | fi 33 | 34 | 35 | # Fetch and display Prometheus targets 36 | print_subheader "Fetching active targets from API" 37 | response=$(curl -s -X GET "http://localhost:30095/api/v1/targets?state=active") 38 | 39 | # Check if the response is empty 40 | if [ -z "$response" ]; then 41 | print_error "No active targets found. Exiting..." 42 | exit 1 43 | fi 44 | 45 | # Use jq to filter out and display the scrapePool (target group) 46 | print_info "Active scrape pools found:" 47 | echo "$response" | jq -r '.data.activeTargets[] | .scrapePool' -------------------------------------------------------------------------------- /nssdc/thanos-objstore-config.yaml: -------------------------------------------------------------------------------- 1 | type: s3 2 | config: 3 | bucket: monarch-thanos 4 | endpoint: "${MONARCH_MINIO_ENDPOINT}" 5 | access_key: ${MONARCH_MINIO_ACCESS_KEY} 6 | secret_key: ${MONARCH_MINIO_SECRET_KEY} 7 | insecure: true 8 | -------------------------------------------------------------------------------- /nssdc/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | KUBE_PROMETHEUS_STACK_VER="51.9.4" 3 | HELM_REPO_URL="https://prometheus-community.github.io/helm-charts" 4 | HELM_REPO_NAME="prometheus-community" 5 | NAMESPACE="monarch" 6 | 7 | kubectl -n $NAMESPACE delete secret thanos-objstore-config --ignore-not-found 8 | helm uninstall nssdc \ 9 | --namespace $NAMESPACE \ -------------------------------------------------------------------------------- /nssdc/values.yaml: -------------------------------------------------------------------------------- 1 | fullnameOverride: nssdc 2 | 3 | alertmanager: 4 | enabled: false 5 | 6 | grafana: 7 | enabled: false 8 | 9 | prometheus: 10 | enabled: true 11 | 12 | thanosService: 13 | enabled: false 14 | 15 | thanosServiceMonitor: 16 | enabled: false 17 | type: NodePort 18 | 19 | thanosServiceExternal: 20 | enabled: true 21 | type: NodePort 22 | nodePort: 30905 23 | httpNodePort: 30906 24 | 25 | service: 26 | type: NodePort 27 | nodePort: 30095 28 | 29 | serviceMonitor: 30 | interval: "${MONARCH_MONITORING_INTERVAL}" 31 | selfMonitor: true 32 | 33 | prometheusSpec: 34 | externalLabels: 35 | cluster: core 36 | 37 | enableRemoteWriteReceiver: true 38 | 39 | scrapeInterval: "${MONARCH_MONITORING_INTERVAL}" 40 | serviceMonitorSelectorNilUsesHelmValues: false 41 | 42 | serviceMonitorNamespaceSelector: 43 | matchExpressions: 44 | - key: kubernetes.io/metadata.name 45 | operator: In 46 | values: 47 | - open5gs 48 | - monarch 49 | 50 | retention: 5d 51 | 52 | thanos: 53 | image: quay.io/thanos/thanos:v0.31.0 54 | objectStorageConfig: 55 | key: thanos.yaml 56 | name: thanos-objstore-config 57 | 58 | # additionalScrapeConfigsSecret: 59 | # enabled: true 60 | # name: additional-scrape-configs 61 | # key: additional-scrape-configs.yaml 62 | 63 | additionalScrapeConfigs: 64 | - job_name: "monarch-service" 65 | scrape_interval: 1s 66 | kubernetes_sd_configs: 67 | - role: service 68 | namespaces: 69 | names: 70 | - 'open5gs' 71 | - 'monarch' 72 | relabel_configs: 73 | - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape] 74 | action: keep 75 | regex: true 76 | 77 | nodeExporter: 78 | enabled: false 79 | 80 | kubeStateMetrics: 81 | enabled: true 82 | 83 | kubeApiServer: 84 | enabled: false 85 | 86 | coreDns: 87 | enabled: false 88 | 89 | kubeControllerManager: 90 | enabled: false 91 | 92 | kubeEtcd: 93 | enabled: false 94 | 95 | kubeProxy: 96 | enabled: false 97 | 98 | kubeScheduler: 99 | enabled: false 100 | 101 | kubelet: 102 | enabled: true 103 | 104 | prometheusOperator: 105 | kubeletService: 106 | enabled: true 107 | 108 | prometheus-node-exporter: 109 | prometheus: 110 | monitor: 111 | enabled: true 112 | relabelings: 113 | - sourceLabels: [__meta_kubernetes_endpoint_node_name] 114 | targetLabel: node 115 | -------------------------------------------------------------------------------- /remove-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | WORKING_DIR="$(pwd)" 3 | ./remove-monarch-nss.sh 4 | ./remove-monarch-core.sh 5 | ./remove-external.sh 6 | 7 | -------------------------------------------------------------------------------- /remove-external.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Function to print in color 4 | print_header() { 5 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 6 | } 7 | 8 | print_success() { 9 | echo -e "\e[1;32m$1\e[0m" 10 | } 11 | 12 | print_error() { 13 | echo -e "\e[1;31mERROR: $1\e[0m" 14 | } 15 | 16 | NAMESPACE="monarch" 17 | USER="$(whoami)" 18 | WORKING_DIR="$(pwd)" 19 | 20 | # Check if the namespace exists 21 | print_header "Checking if namespace '$NAMESPACE' exists" 22 | kubectl get namespace $NAMESPACE 2>/dev/null 23 | if [ $? -ne 0 ]; then 24 | print_error "Namespace '$NAMESPACE' not found. Exiting removal process." 25 | exit 1 26 | fi 27 | 28 | 29 | print_header "Deleting Monarch external components (i.e., Service Orchestrator and NFV Orchestrator)" 30 | cd $WORKING_DIR/service_orchestrator 31 | ./uninstall.sh 32 | 33 | 34 | cd $WORKING_DIR/nfv_orchestrator 35 | ./uninstall.sh 36 | 37 | print_success "External components removed." -------------------------------------------------------------------------------- /remove-monarch-core.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Function to print in color 4 | print_header() { 5 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 6 | } 7 | 8 | print_success() { 9 | echo -e "\e[1;32m$1\e[0m" 10 | } 11 | 12 | print_error() { 13 | echo -e "\e[1;31mERROR: $1\e[0m" 14 | } 15 | 16 | 17 | # Function to wait for a pod to be ready based on its label 18 | wait_for_pod_deletion() { 19 | local label_key=$1 20 | local label_value=$2 21 | echo "Waiting for pod with label $label_key=$label_value to be deleted in namespace $NAMESPACE..." 22 | 23 | while [ "$(kubectl get pods -n "$NAMESPACE" -l="$label_key=$label_value" -o jsonpath='{.items[*].metadata.name}')" != "" ]; do 24 | sleep 5 25 | echo "Waiting for pod $label_value to be deleted..." 26 | done 27 | print_success "Pod $label_value is deleted." 28 | } 29 | 30 | # Set the namespace for Monarch 31 | NAMESPACE="monarch" 32 | USER="$(whoami)" 33 | WORKING_DIR="$(pwd)" 34 | 35 | print_header "Removing Data Store (Monarch Core [1/5])" 36 | cd $WORKING_DIR/data_store 37 | ./uninstall.sh 38 | wait_for_pod_deletion "app" "minio" 39 | wait_for_pod_deletion "app.kubernetes.io/name" "mongodb" 40 | print_success "Data store removed." 41 | 42 | print_header "Removing Data Distribution (Monarch Core [2/5])" 43 | cd $WORKING_DIR/data_distribution 44 | ./uninstall.sh 45 | wait_for_pod_deletion "app.kubernetes.io/component" "storegateway" 46 | wait_for_pod_deletion "app.kubernetes.io/component" "receive" 47 | print_success "Data Distribution removed." 48 | 49 | print_header "Removing Data Visualization (Monarch Core [3/5])" 50 | cd $WORKING_DIR/data_visualization 51 | ./uninstall.sh 52 | wait_for_pod_deletion "app.kubernetes.io/name" "grafana" 53 | print_success "Data Visualization removed." 54 | 55 | print_header "Removing Monitoring Manager (Monarch Core [4/5])" 56 | cd $WORKING_DIR/monitoring_manager 57 | ./uninstall.sh 58 | wait_for_pod_deletion "app.kubernetes.io/component" "monitoring-manager" 59 | print_success "Monitoring manager removed." 60 | 61 | print_header "Removing Request Translator (Monarch Core [5/5])" 62 | cd $WORKING_DIR/request_translator 63 | ./uninstall.sh 64 | wait_for_pod_deletion "component" "request-translator" 65 | print_success "Request Translator removed." -------------------------------------------------------------------------------- /remove-monarch-nss.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | print_header() { 3 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 4 | } 5 | 6 | print_success() { 7 | echo -e "\e[1;32m$1\e[0m" 8 | } 9 | 10 | print_error() { 11 | echo -e "\e[1;31mERROR: $1\e[0m" 12 | } 13 | 14 | 15 | # Function to wait for a pod to be ready based on its label 16 | wait_for_pod_deletion() { 17 | local label_key=$1 18 | local label_value=$2 19 | echo "Waiting for pod with label $label_key=$label_value to be deleted in namespace $NAMESPACE..." 20 | 21 | while [ "$(kubectl get pods -n "$NAMESPACE" -l="$label_key=$label_value" -o jsonpath='{.items[*].metadata.name}')" != "" ]; do 22 | sleep 5 23 | echo "Waiting for pod $label_value to be deleted..." 24 | done 25 | print_success "Pod $label_value is deleted." 26 | } 27 | 28 | # Set the namespace for Monarch 29 | NAMESPACE="monarch" 30 | USER="$(whoami)" 31 | WORKING_DIR="$(pwd)" 32 | 33 | print_header "Removing NSSDC (Monarch NSS [1/1])" 34 | cd $WORKING_DIR/nssdc 35 | ./uninstall.sh 36 | wait_for_pod_deletion "app.kubernetes.io/name" "prometheus" 37 | print_success "NSSDC removed." -------------------------------------------------------------------------------- /replace-node-ip.sh: -------------------------------------------------------------------------------- 1 | # Fetch the node IP using your existing script 2 | NODE_IP=$(./get-node-ip.sh) 3 | 4 | # Check if NODE_IP was fetched successfully 5 | if [[ -z "$NODE_IP" ]]; then 6 | echo "Error: Could not retrieve NODE_IP." 7 | exit 1 8 | fi 9 | 10 | # Replace NODE_IP in the .env file 11 | sed -i "s/^NODE_IP=.*/NODE_IP=\"$NODE_IP\"/" .env 12 | 13 | echo "NODE_IP updated to $NODE_IP in .env file." -------------------------------------------------------------------------------- /request_translator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | LABEL maintainer="Niloy Saha " 3 | LABEL description="Request Translator v1.0.0 for Monarch" 4 | 5 | 6 | RUN mkdir -p /monarch/ 7 | 8 | WORKDIR /monarch 9 | COPY /app/requirements.txt ./ 10 | RUN apt-get update \ 11 | && apt-get install -y --no-install-recommends \ 12 | vim iputils-ping curl \ 13 | && apt-get autoremove -y && apt-get autoclean 14 | RUN pip install -r requirements.txt 15 | 16 | COPY app /monarch/app 17 | COPY run.py ./ 18 | 19 | 20 | EXPOSE 5000 21 | 22 | CMD python3 run.py -------------------------------------------------------------------------------- /request_translator/README.md: -------------------------------------------------------------------------------- 1 | # request-translator 2 | This component implements an abstraction layer that translates high-level monitoring requests specified by the monitoring API into low-level monitoring directives understood by the Monitoring Manager (e.g., which NF instance to monitor). 3 | 4 | For now, it uses Kubernetes API as service orchestrator. 5 | 6 | ## How to run 7 | First, we need to create a secret for the kubeconfig file. This is necessary to access the Kubernetes cluster. The secret is created by running the following script: 8 | 9 | ```bash 10 | create-kubeconfig-secret.sh 11 | ``` 12 | 13 | **Note**: The script should be updated to point to the correct kubeconfig file. 14 | -------------------------------------------------------------------------------- /request_translator/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/request_translator/app/__init__.py -------------------------------------------------------------------------------- /request_translator/app/comm_manager.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | from app.logger import setup_logger 4 | 5 | 6 | class CommunicationManager: 7 | def __init__(self, monitoring_manager_uri): 8 | self.monitoring_manager_uri = monitoring_manager_uri 9 | self.logger = setup_logger("comm_manager") 10 | self.connect_to_monitoring_manager() 11 | 12 | def is_monitoring_manager_available(self): 13 | try: 14 | response = requests.get(self.monitoring_manager_uri + "/api/health") 15 | return response.status_code == 200 16 | except requests.exceptions.RequestException: 17 | return False 18 | 19 | def connect_to_monitoring_manager(self, max_retries=5, wait_time=5): 20 | attempt = 0 21 | while attempt < max_retries: 22 | self.logger.info(f"Attempt {attempt + 1} to connect to Monitoring Manager at {self.monitoring_manager_uri}") 23 | if self.is_monitoring_manager_available(): 24 | self.logger.info("Successfully connected to Monitoring Manager") 25 | return True 26 | attempt += 1 27 | time.sleep(wait_time) 28 | self.logger.error(f"Could not connect to Monitoring Manager after {max_retries} attempts") 29 | exit(1) 30 | 31 | def send_directive(self, directive): 32 | """ 33 | Send directive to Monitoring Manager 34 | """ 35 | monitoring_manager_url = self.monitoring_manager_uri + "/api/monitoring-directives" 36 | try: 37 | response = requests.post(monitoring_manager_url, json=directive) 38 | if response.status_code == 200: 39 | self.logger.info(f"Directive sent successfully with status code {response.status_code}") 40 | return True 41 | else: 42 | self.logger.error(f"Failed to send directive: {response.status_code}") 43 | return False 44 | except Exception as e: 45 | self.logger.error(f"Error in sending directive to Monitoring Manager: {e}") 46 | return False 47 | 48 | def send_delete_directive(self, directive): 49 | """ 50 | Send directive to Monitoring Manager to delete a monitoring request 51 | """ 52 | monitoring_manager_url = self.monitoring_manager_uri + "/api/monitoring-directives/delete" 53 | try: 54 | response = requests.post(monitoring_manager_url, json=directive) 55 | if response.status_code == 200: 56 | self.logger.info(f"Delete directive sent successfully with status code {response.status_code}") 57 | return True 58 | else: 59 | self.logger.error(f"Failed to send delete directive: {response.status_code}") 60 | return False 61 | except Exception as e: 62 | self.logger.error(f"Error in sending delete directive to Monitoring Manager: {e}") 63 | return False 64 | -------------------------------------------------------------------------------- /request_translator/app/db_manager.py: -------------------------------------------------------------------------------- 1 | from app.logger import setup_logger 2 | from pymongo import MongoClient, errors 3 | import time 4 | 5 | 6 | class DatabaseManager: 7 | def __init__(self, mongodb_uri): 8 | self.mongodb_uri = mongodb_uri 9 | self.logger = setup_logger("database_manager") 10 | self.client = self.connect_to_mongodb() 11 | self.db = self.client.monarch 12 | 13 | def connect_to_mongodb(self, max_retries=5, wait_time=5): 14 | attempt = 0 15 | while attempt < max_retries: 16 | try: 17 | self.logger.info(f"Attempt {attempt + 1} to connect to MongoDB at {self.mongodb_uri}") 18 | client = MongoClient(self.mongodb_uri, serverSelectionTimeoutMS=5000) 19 | # Attempt to fetch the server info to ensure connection is established 20 | client.server_info() 21 | self.logger.info("Successfully connected to MongoDB") 22 | return client 23 | except errors.ServerSelectionTimeoutError as e: 24 | self.logger.warning(f"Connection attempt {attempt + 1} failed: {e}") 25 | attempt += 1 26 | time.sleep(wait_time) 27 | self.logger.error(f"Could not connect to MongoDB after {max_retries} attempts") 28 | exit(1) 29 | -------------------------------------------------------------------------------- /request_translator/app/kpi_manager.py: -------------------------------------------------------------------------------- 1 | import json 2 | from app.logger import setup_logger 3 | 4 | 5 | class KPIManager: 6 | def __init__(self): 7 | self.logger = setup_logger("kpi_manager") 8 | self.load_supported_kpis("app/supported_kpis.json") 9 | 10 | def load_supported_kpis(self, file_path): 11 | self.logger.info(f"Loading supported KPIs from {file_path}") 12 | with open(file_path, "r") as file: 13 | self.supported_kpis = json.load(file) 14 | 15 | def list_supported_kpis(self): 16 | return [ 17 | { 18 | "kpi_name": kpi["kpi_name"], 19 | "kpi_description": kpi["kpi_description"], 20 | "kpi_unit": kpi["kpi_unit"], 21 | } 22 | for kpi in self.supported_kpis 23 | ] 24 | 25 | def is_kpi_supported(self, monitoring_request): 26 | kpi = monitoring_request.get("kpi") 27 | kpi_name = kpi.get("kpi_name") 28 | return any(kpi["kpi_name"] == kpi_name for kpi in self.supported_kpis) 29 | -------------------------------------------------------------------------------- /request_translator/app/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | def setup_logger(name): 5 | logger = logging.getLogger(name) 6 | logger.setLevel(logging.INFO) 7 | 8 | ch = logging.StreamHandler() 9 | ch.setLevel(logging.INFO) 10 | 11 | formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") 12 | ch.setFormatter(formatter) 13 | 14 | if not logger.hasHandlers(): 15 | logger.addHandler(ch) 16 | 17 | return logger 18 | -------------------------------------------------------------------------------- /request_translator/app/request_translator.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Author: Niloy Saha 3 | # Email: niloysaha.ns@gmail.com 4 | # version ='1.0.0' 5 | # --------------------------------------------------------------------------- 6 | """ 7 | Request translator component of Monarch. 8 | """ 9 | 10 | from flask import Flask, request, jsonify 11 | from jsonschema import validate, ValidationError 12 | import shortuuid 13 | import json 14 | import requests 15 | from app.kpi_manager import KPIManager 16 | from app.service_orchestrator import ServiceOrchestratorManager 17 | from app.db_manager import DatabaseManager 18 | from app.comm_manager import CommunicationManager 19 | from app.translation_manager import TranslationManager 20 | from app.logger import setup_logger 21 | 22 | 23 | class RequestTranslator: 24 | def __init__(self, monitoring_manager_uri, mongodb_uri, service_orchestrator_uri): 25 | self.app = Flask(__name__) 26 | self.logger = setup_logger("request_translator") 27 | self.monitoring_manager_uri = monitoring_manager_uri 28 | self.service_orchestrator_uri = service_orchestrator_uri 29 | self.mongodb_uri = mongodb_uri 30 | self.monitoring_requests = {} 31 | 32 | self.kpi_manager = KPIManager() 33 | self.service_orchestrator = ServiceOrchestratorManager(service_orchestrator_uri) 34 | self.database_manager = DatabaseManager(mongodb_uri) 35 | self.comm_manager = CommunicationManager(monitoring_manager_uri) 36 | self.translation_manager = TranslationManager(self.service_orchestrator) 37 | 38 | self._load_configuration() 39 | self._set_routes() 40 | 41 | def _load_configuration(self): 42 | with open("app/schema.json", "r") as file: 43 | self.schema = json.load(file) 44 | 45 | def _set_routes(self): 46 | self.app.add_url_rule( 47 | "/api/monitoring-requests", 48 | "submit_monitoring_request", 49 | self.submit_monitoring_request, 50 | methods=["POST"], 51 | ) 52 | self.app.add_url_rule( 53 | "/api/monitoring-requests/", 54 | "get_monitoring_request", 55 | self.get_monitoring_request, 56 | methods=["GET"], 57 | ) 58 | self.app.add_url_rule( 59 | "/api/monitoring-requests", 60 | "get_all_monitoring_requests", 61 | self.get_all_monitoring_requests, 62 | methods=["GET"], 63 | ) 64 | self.app.add_url_rule( 65 | "/api/supported-kpis", 66 | "get_supported_kpis", 67 | self.get_supported_kpis, 68 | methods=["GET"], 69 | ) 70 | self.app.add_url_rule( 71 | "/api/monitoring-requests/delete/", 72 | "delete_monitoring_request", 73 | self.delete_monitoring_request, 74 | methods=["DELETE"], 75 | ) 76 | self.app.add_url_rule("/api/health", "health_check", self.health_check, methods=["GET"]) 77 | 78 | def health_check(self): 79 | return jsonify({"status": "success", "message": "Request Translator is healthy"}), 200 80 | 81 | def submit_monitoring_request(self): 82 | data = request.get_json() 83 | try: 84 | validate(instance=data, schema=self.schema) 85 | if not self.kpi_manager.is_kpi_supported(data): 86 | return jsonify({"status": "error", "message": "KPI is not supported"}), 400 87 | 88 | request_id = shortuuid.uuid() # Generate a unique request_id 89 | self.monitoring_requests[request_id] = data 90 | directive = self.translation_manager.translate_request(data, request_id) 91 | 92 | if self.comm_manager.send_directive(directive): 93 | return jsonify({"status": "success", "request_id": request_id}), 200 94 | else: 95 | self.monitoring_requests.pop(request_id) # Remove the request if it fails to send to Monitoring Manager 96 | return jsonify({"status": "error", "message": "Failed to submit monitoring request"}), 500 97 | except ValidationError as e: 98 | return jsonify({"status": "error", "message": str(e)}), 400 99 | 100 | def get_monitoring_request(self, request_id): 101 | """ 102 | Retrieve monitoring request by request_id 103 | """ 104 | request_data = self.monitoring_requests.get(request_id) 105 | if request_data: 106 | return jsonify({"status": "success", "request_id": request_id, "data": request_data}) 107 | else: 108 | return jsonify({"status": "error", "message": "Monitoring request not found"}), 404 109 | 110 | def get_all_monitoring_requests(self): 111 | # Return all monitoring requests 112 | return jsonify({"status": "success", "data": self.monitoring_requests}) 113 | 114 | def get_supported_kpis(self): 115 | return jsonify({"status": "success", "supported_kpis": self.kpi_manager.list_supported_kpis()}) 116 | 117 | def run(self, port): 118 | self.app.run(debug=True, port=port, host="0.0.0.0") 119 | 120 | def delete_monitoring_request(self, request_id): 121 | """ 122 | Delete monitoring request by request_id 123 | """ 124 | if request_id in self.monitoring_requests: 125 | request = self.monitoring_requests[request_id] 126 | kpi_name = request["kpi"]["kpi_name"] 127 | delete_directive = {"request_id": request_id, "action": "delete", "kpi_name": kpi_name} 128 | if self.comm_manager.send_delete_directive(delete_directive): 129 | del self.monitoring_requests[request_id] 130 | return jsonify({"status": "success", "message": "Monitoring request deleted"}), 200 131 | else: 132 | return jsonify({"status": "error", "message": "Failed to delete monitoring request"}), 500 133 | else: 134 | return jsonify({"status": "error", "message": "Monitoring request not found"}), 404 135 | -------------------------------------------------------------------------------- /request_translator/app/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | python-dotenv 3 | pymongo 4 | flask 5 | jsonschema 6 | shortuuid -------------------------------------------------------------------------------- /request_translator/app/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "api_version": { 5 | "type": "string" 6 | }, 7 | "request_description": { 8 | "type": "string" 9 | }, 10 | "scope": { 11 | "type": "object", 12 | "properties": { 13 | "scope_type": { 14 | "type": "string" 15 | }, 16 | "scope_id": { 17 | "type": "string" 18 | } 19 | }, 20 | "required": [ 21 | "scope_type", 22 | "scope_id" 23 | ] 24 | }, 25 | "kpi": { 26 | "type": "object", 27 | "properties": { 28 | "kpi_name": { 29 | "type": "string" 30 | }, 31 | "kpi_description": { 32 | "type": "string" 33 | }, 34 | "sub_counter": { 35 | "type": "object", 36 | "properties": { 37 | "sub_counter_type": { 38 | "type": "string" 39 | }, 40 | "sub_counter_ids": { 41 | "type": "array", 42 | "items": { 43 | "type": "string" 44 | } 45 | } 46 | }, 47 | "required": [ 48 | "sub_counter_type", 49 | "sub_counter_ids" 50 | ] 51 | }, 52 | "units": { 53 | "type": "string" 54 | } 55 | }, 56 | "required": [ 57 | "kpi_name", 58 | "kpi_description", 59 | "sub_counter", 60 | "units" 61 | ] 62 | }, 63 | "duration": { 64 | "type": "object", 65 | "properties": { 66 | "start_time": { 67 | "type": "string", 68 | "format": "date-time" 69 | }, 70 | "end_time": { 71 | "type": "string", 72 | "format": "date-time" 73 | } 74 | }, 75 | "required": [ 76 | "start_time", 77 | "end_time" 78 | ] 79 | }, 80 | "monitoring_interval": { 81 | "type": "object", 82 | "properties": { 83 | "adaptive": { 84 | "type": "boolean" 85 | }, 86 | "interval_seconds": { 87 | "type": "number" 88 | } 89 | }, 90 | "required": [ 91 | "adaptive", 92 | "interval_seconds" 93 | ] 94 | } 95 | }, 96 | "required": [ 97 | "api_version", 98 | "request_description", 99 | "scope", 100 | "kpi", 101 | "duration", 102 | "monitoring_interval" 103 | ] 104 | } -------------------------------------------------------------------------------- /request_translator/app/service_orchestrator.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | import time 4 | from app.logger import setup_logger 5 | 6 | 7 | class ServiceOrchestratorManager: 8 | """ 9 | Class that interacts with the Service Orchestrator to retrieve information about the slice components. 10 | """ 11 | 12 | def __init__(self, service_orchestrator_uri): 13 | self.service_orchestrator_uri = service_orchestrator_uri 14 | self.logger = setup_logger("service_orchestrator") 15 | self.connect_to_service_orchestrator() 16 | 17 | def is_service_orchestrator_available(self): 18 | try: 19 | response = requests.get(self.service_orchestrator_uri + "/api/health") 20 | return response.status_code == 200 21 | except requests.exceptions.RequestException: 22 | return False 23 | 24 | def connect_to_service_orchestrator(self, max_retries=5, wait_time=5): 25 | attempt = 0 26 | while attempt < max_retries: 27 | self.logger.info( 28 | f"Attempt {attempt + 1} to connect to Service Orchestrator at {self.service_orchestrator_uri}" 29 | ) 30 | if self.is_service_orchestrator_available(): 31 | self.logger.info("Successfully connected to Service Orchestrator") 32 | return True 33 | attempt += 1 34 | time.sleep(wait_time) 35 | self.logger.error(f"Could not connect to Service Orchestrator after {max_retries} attempts") 36 | exit(1) 37 | 38 | def get_slice_components(self, slice_id, nsi=None): 39 | try: 40 | response = requests.get(self.service_orchestrator_uri + f"/slices/{slice_id}") 41 | if response.status_code == 200: 42 | self.logger.info(f"Successfully retrieved slice components for slice ID {slice_id}") 43 | pods = response.json()["pods"] 44 | return pods 45 | else: 46 | self.logger.error(f"Error retrieving slice components: {response.text}") 47 | return None 48 | except requests.exceptions.RequestException as e: 49 | self.logger.error(f"Error retrieving slice components: {str(e)}") 50 | return None 51 | -------------------------------------------------------------------------------- /request_translator/app/supported_kpis.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "kpi_name": "slice_throughput", 4 | "kpi_description": "Throughput per slice", 5 | "kpi_unit": "bits/sec", 6 | "kpi_prom_metric_name": "slice_throughput" 7 | } 8 | ] -------------------------------------------------------------------------------- /request_translator/app/translation_manager.py: -------------------------------------------------------------------------------- 1 | from app.logger import setup_logger 2 | from app.service_orchestrator import ServiceOrchestratorManager 3 | 4 | 5 | class TranslationManager: 6 | def __init__(self, service_orchestrator: ServiceOrchestratorManager): 7 | self.logger = setup_logger("translation_manager") 8 | self.service_orchestrator = service_orchestrator 9 | 10 | def translate_request(self, request, request_id): 11 | self.logger.info("Translating request...") 12 | kpi_name = request["kpi"]["kpi_name"] 13 | 14 | if kpi_name == "slice_throughput": 15 | components = self.translate_slice_throughput(request) 16 | 17 | else: 18 | raise NotImplementedError(f"KPI '{kpi_name}' is not supported") 19 | 20 | directive = { 21 | "request_id": request_id, 22 | "kpi_name": kpi_name, 23 | "action": "create", 24 | "components": components, 25 | "interval": request["monitoring_interval"]["interval_seconds"], 26 | } 27 | 28 | self.logger.debug(f"Translated directive: {directive}") 29 | return directive 30 | 31 | def translate_slice_throughput(self, request): 32 | """ 33 | Translates a slice throughput request into metrics from SMF and UPF to monitor. 34 | 3GPP 28.554 Section 6.3.2 and 6.3.3 35 | """ 36 | self.logger.info("Translating slice throughput request...") 37 | snssais = request["kpi"]["sub_counter"]["sub_counter_ids"] 38 | self.logger.info(f"NSSAIs: {snssais}") 39 | 40 | components_to_monitor = [] 41 | 42 | # get pod_info for the NFs by interacting with the service orchestrator 43 | for snssai in snssais: 44 | pod_infos = self.service_orchestrator.get_slice_components(snssai) 45 | self.logger.info(f"Pod info for SNSSAI {snssai}: {pod_infos}") 46 | 47 | for pod_info in pod_infos: 48 | component_info = {} 49 | if pod_info["nf"] == "smf": 50 | component_info["type"] = "pod" 51 | component_info["nf"] = "smf" 52 | component_info["nss"] = pod_info["nss"] 53 | component_info["pod_name"] = pod_info["name"] 54 | component_info["pod_ip"] = pod_info["pod_ip"] 55 | component_info["metrics"] = ["fivegs_smffunction_sm_seid_session"] 56 | components_to_monitor.append(component_info) 57 | 58 | elif pod_info["nf"] == "upf": 59 | component_info["type"] = "pod" 60 | component_info["nf"] = "upf" 61 | component_info["nss"] = pod_info["nss"] 62 | component_info["pod_name"] = pod_info["name"] 63 | component_info["pod_ip"] = pod_info["pod_ip"] 64 | component_info["metrics"] = [ 65 | "fivegs_ep_n3_gtp_outdatavolumen3upf_seid_total", 66 | "fivegs_ep_n3_gtp_outdatavolumen3upf_seid_total", 67 | ] 68 | pod_info["metrics"] = [ 69 | "fivegs_ep_n3_gtp_outdatavolumen3upf_seid_total", 70 | "fivegs_ep_n3_gtp_outdatavolumen3upf_seid_total", 71 | ] 72 | components_to_monitor.append(component_info) 73 | 74 | self.logger.debug(f"Components to monitor: {components_to_monitor}") 75 | return components_to_monitor 76 | -------------------------------------------------------------------------------- /request_translator/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NAMESPACE="monarch" 3 | MODULE_NAME="request-translator" 4 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | KUSTOMIZATION_FILE="$SCRIPT_DIR/manifests/kustomization.yaml" 6 | cd "$SCRIPT_DIR" 7 | set -o allexport; source ../.env; set +o allexport 8 | 9 | # Check if SERVICE_ORCHESTRATOR_URI is set 10 | if [ -z "$SERVICE_ORCHESTRATOR_URI" ]; then 11 | echo "SERVICE_ORCHESTRATOR_URI is not set in the .env file" 12 | exit 1 13 | fi 14 | 15 | kubectl get namespace $NAMESPACE 2>/dev/null || kubectl create namespace $NAMESPACE 16 | 17 | kubectl create configmap slice-components-configmap --from-file=slice_components.json -n $NAMESPACE 18 | envsubst < manifests/deployment.yaml | kubectl apply -f - 19 | envsubst < manifests/service.yaml | kubectl apply -f - 20 | -------------------------------------------------------------------------------- /request_translator/manifests/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: request-translator 5 | namespace: monarch 6 | labels: 7 | app: monarch 8 | component: request-translator 9 | spec: 10 | selector: 11 | matchLabels: 12 | app: monarch 13 | component: request-translator 14 | replicas: 1 15 | template: 16 | metadata: 17 | labels: 18 | app: monarch 19 | component: request-translator 20 | spec: 21 | containers: 22 | - image: ghcr.io/niloysh/request-translator:v1.0.0 23 | name: request-translator 24 | imagePullPolicy: Always 25 | ports: 26 | - name: api 27 | containerPort: 7000 28 | command: ["/bin/bash", "-c", "--"] 29 | args: ["python -u run.py"] 30 | # args: ["while true; do sleep 30000000; done;"] 31 | env: 32 | - name: MONITORING_MANAGER_URI 33 | value: http://monitoring-manager-service.monarch.svc.cluster.local:6000 34 | - name: MONARCH_MONGO_URI 35 | value: mongodb://datastore-mongodb.monarch.svc.cluster.local:27017 36 | - name: SERVICE_ORCHESTRATOR_URI 37 | value: ${SERVICE_ORCHESTRATOR_URI} 38 | resources: 39 | requests: 40 | memory: "100Mi" 41 | cpu: "100m" 42 | limits: 43 | memory: "200Mi" 44 | cpu: "200m" 45 | readinessProbe: 46 | httpGet: 47 | path: /api/health 48 | port: 7000 49 | initialDelaySeconds: 5 50 | periodSeconds: 10 51 | timeoutSeconds: 2 52 | successThreshold: 1 53 | failureThreshold: 3 54 | restartPolicy: Always 55 | -------------------------------------------------------------------------------- /request_translator/manifests/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: request-translator-service 5 | namespace: monarch 6 | labels: 7 | app: monarch 8 | component: request-translator 9 | spec: 10 | type: NodePort 11 | ports: 12 | - name: api # expose server port 13 | port: 7000 # which port is exposed 14 | targetPort: api # port name in pod 15 | nodePort: 30700 16 | selector: 17 | app: monarch # target pods 18 | component: request-translator 19 | -------------------------------------------------------------------------------- /request_translator/port-forward-mongodb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Forwarding the port of the MongoDB service to the local machine 3 | ## Needed for local development 4 | kubectl port-forward service/datastore-mongodb -n monarch 27017:27017 -------------------------------------------------------------------------------- /request_translator/requests/request_invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_version": "1.0", 3 | "request_description": "Monitoring request for slice throughput", 4 | "scope": { 5 | "scope_type": "slice" 6 | }, 7 | "kpi": { 8 | "kpi_name": "slice_throughput", 9 | "kpi_description": "Throughput of the network slice", 10 | "sub_counter": { 11 | "sub_counter_type": "SNSSAI", 12 | "sub_counter_ids": [ 13 | "1-000001", 14 | "2-000002" 15 | ] 16 | }, 17 | "units": "Mbps" 18 | }, 19 | "duration": { 20 | "start_time": "2023-12-01T00:00:00Z", 21 | "end_time": "2023-12-01T00:05:00Z" 22 | }, 23 | "monitoring_interval": { 24 | "adaptive": true, 25 | "interval_seconds": 1 26 | } 27 | } -------------------------------------------------------------------------------- /request_translator/requests/request_nf.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_version": "1.0", 3 | "request_description": "Monitoring request for the number of QoS flows", 4 | "scope": { 5 | "scope_type": "nf", 6 | "scope_id": "SMF01" 7 | }, 8 | "kpi": { 9 | "kpi_name": "smf.num_qos_flows", 10 | "kpi_description": "Number of QoS flows at the SMF", 11 | "sub_counter": { 12 | "sub_counter_type": "5QI", 13 | "sub_counter_ids": [ 14 | "1", 15 | "6" 16 | ] 17 | }, 18 | "units": "count" 19 | }, 20 | "duration": { 21 | "start_time": "2023-12-01T00:00:00Z", 22 | "end_time": "2023-12-01T00:30:00Z" 23 | }, 24 | "monitoring_interval": { 25 | "adaptive": false, 26 | "interval_seconds": 5 27 | } 28 | } -------------------------------------------------------------------------------- /request_translator/requests/request_slice.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_version": "1.0", 3 | "request_description": "Monitoring request for slice throughput", 4 | "scope": { 5 | "scope_type": "slice", 6 | "scope_id": "NSI01" 7 | }, 8 | "kpi": { 9 | "kpi_name": "slice_throughput", 10 | "kpi_description": "Throughput of the network slice", 11 | "sub_counter": { 12 | "sub_counter_type": "SNSSAI", 13 | "sub_counter_ids": [ 14 | "1-000001", 15 | "2-000002" 16 | ] 17 | }, 18 | "units": "Mbps" 19 | }, 20 | "duration": { 21 | "start_time": "2023-12-01T00:00:00Z", 22 | "end_time": "2023-12-01T00:05:00Z" 23 | }, 24 | "monitoring_interval": { 25 | "adaptive": true, 26 | "interval_seconds": 1 27 | } 28 | } -------------------------------------------------------------------------------- /request_translator/run.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | from app.request_translator import RequestTranslator 4 | from app.logger import setup_logger 5 | from dotenv import load_dotenv 6 | 7 | load_dotenv() 8 | MONITORING_MANAGER_URI = os.getenv("MONITORING_MANAGER_URI", "http://localhost:6000") 9 | SERVICE_ORCHESTRATOR_URI = os.getenv("SERVICE_ORCHESTRATOR_URI", "http://localhost:5001") 10 | REQUEST_TRANSLATOR_PORT = int(os.getenv("REQUEST_TRANSLATOR_PORT", 7000)) 11 | MONARCH_MONGO_URI = os.getenv("MONARCH_MONGO_URI", "mongodb://localhost:27017/") 12 | DEFAULT_SLICE_COMPONENTS_FILE = "app/slice_components.json" 13 | 14 | 15 | def main(): 16 | logger = setup_logger("app") 17 | logger.info("Starting RequestTranslator service") 18 | logger.info(f"Monitoring Manager URI: {MONITORING_MANAGER_URI}") 19 | logger.info(f"Monarch MongoDB URI: {MONARCH_MONGO_URI}") 20 | logger.info(f"Service Orchestrator URI: {SERVICE_ORCHESTRATOR_URI}") 21 | 22 | app = RequestTranslator(MONITORING_MANAGER_URI, MONARCH_MONGO_URI, SERVICE_ORCHESTRATOR_URI) 23 | app.run(port=REQUEST_TRANSLATOR_PORT) 24 | 25 | 26 | if __name__ == "__main__": 27 | main() 28 | -------------------------------------------------------------------------------- /request_translator/slice_components.json: -------------------------------------------------------------------------------- 1 | { 2 | "1-000001": [ 3 | { 4 | "nf": "upf1", 5 | "nss": "edge" 6 | }, 7 | { 8 | "nf": "smf1", 9 | "nss": "core" 10 | } 11 | ], 12 | "2-000002": [ 13 | { 14 | "nf": "upf2", 15 | "nss": "edge" 16 | }, 17 | { 18 | "nf": "smf2", 19 | "nss": "core" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /request_translator/test-request-translator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | print_header() { 4 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 5 | } 6 | 7 | print_subheader() { 8 | echo -e "\e[1;36m--- $1 ---\e[0m" 9 | } 10 | 11 | print_success() { 12 | echo -e "\e[1;32m$1\e[0m" 13 | } 14 | 15 | print_error() { 16 | echo -e "\e[1;31mERROR: $1\e[0m" 17 | } 18 | 19 | print_info() { 20 | echo -e "\e[1;33mINFO: $1\e[0m" 21 | } 22 | 23 | # Endpoint URLs 24 | HEALTH_URL="http://localhost:30700/api/health" 25 | KPIS_URL="http://localhost:30700/api/supported-kpis" 26 | 27 | print_header "Testing Request Translator (Monarch Core [5/5])" 28 | print_subheader "Performing health check for Request Translator" 29 | print_info "Checking health status of the service..." 30 | health_response=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL") 31 | 32 | if [ "$health_response" -eq 200 ]; then 33 | print_success "Service health check passed! The service is up and running." 34 | else 35 | print_error "Service health check failed with status code: $health_response." 36 | exit 1 37 | fi 38 | 39 | print_subheader "Checking supported KPIs" 40 | print_info "Fetching supported KPIs..." 41 | kpis_response=$(curl -s "$KPIS_URL") 42 | 43 | if [ -z "$kpis_response" ]; then 44 | print_error "Failed to fetch supported KPIs or received an empty response." 45 | exit 1 46 | fi 47 | 48 | # Display the supported KPIs in a formatted way 49 | print_success "Supported KPIs retrieved successfully:" 50 | echo "$kpis_response" -------------------------------------------------------------------------------- /request_translator/test_api.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import argparse 3 | import json 4 | import os 5 | from dotenv import load_dotenv 6 | 7 | load_dotenv() 8 | MONARCH_REQUEST_TRANSLATOR_URI = os.getenv("MONARCH_REQUEST_TRANSLATOR_URI", "http://127.0.0.1:5000") 9 | 10 | 11 | def test_submit(json_file_path): 12 | # Load the JSON data from the file 13 | with open(json_file_path, "r") as file: 14 | json_data = json.load(file) 15 | 16 | submit_url = f"{MONARCH_REQUEST_TRANSLATOR_URI}/api/monitoring-requests" 17 | 18 | # Send the POST request to the endpoint 19 | response = requests.post(submit_url, json=json_data) 20 | 21 | # Print the response from the server 22 | print(f"Status Code: {response.status_code}") 23 | print(f"Response: {response.json()}") 24 | 25 | 26 | def list_kpis(): 27 | list_kpis_url = f"{MONARCH_REQUEST_TRANSLATOR_URI}/api/supported-kpis" 28 | response = requests.get(list_kpis_url) 29 | 30 | # Print the response from the server 31 | print(f"Status Code: {response.status_code}") 32 | print(f"Response: {response.json()}") 33 | 34 | 35 | def list_monitoring_requests(): 36 | # Send the GET request to list all monitoring requests 37 | list_url = f"{MONARCH_REQUEST_TRANSLATOR_URI}/api/monitoring-requests" 38 | response = requests.get(list_url) 39 | 40 | # Print the response from the server 41 | print(f"Status Code: {response.status_code}") 42 | print(f"Response: {response.json()}") 43 | 44 | 45 | def delete_monitoring_request(request_id): 46 | # Construct the URL for deleting a specific monitoring request 47 | delete_url = f"{MONARCH_REQUEST_TRANSLATOR_URI}/api/monitoring-requests/delete/{request_id}" 48 | 49 | # Send the DELETE request to delete the specified monitoring request 50 | response = requests.delete(delete_url) 51 | 52 | # Print the response from the server 53 | print(f"Status Code: {response.status_code}") 54 | print(f"Response: {response.json()}") 55 | 56 | 57 | if __name__ == "__main__": 58 | parser = argparse.ArgumentParser(description="Test the monitoring endpoint of the Flask API") 59 | parser.add_argument( 60 | "action", 61 | choices=["submit", "list", "kpis", "delete"], 62 | help="Action to perform: submit, list, delete, kpis (list supported KPIs)", 63 | ) 64 | parser.add_argument( 65 | "--json_file", default="requests/request_nf.json", help="Path to the JSON file (only for submit action)" 66 | ) 67 | parser.add_argument("--request_id", help="ID of the request to delete (only for delete action)") 68 | 69 | args = parser.parse_args() 70 | 71 | if args.action == "submit": 72 | test_submit(f"{args.json_file}") 73 | elif args.action == "list": 74 | list_monitoring_requests() 75 | elif args.action == "kpis": 76 | list_kpis(args.url) 77 | elif args.action == "delete": 78 | if args.request_id: 79 | delete_monitoring_request(args.request_id) 80 | else: 81 | print("Error: --request_id is required for delete action") 82 | -------------------------------------------------------------------------------- /request_translator/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NAMESPACE="monarch" 3 | MODULE_NAME="request-translator" 4 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | cd "$SCRIPT_DIR" 6 | set -o allexport; source ../.env; set +o allexport 7 | kubectl get namespace $NAMESPACE 2>/dev/null || kubectl create namespace $NAMESPACE 8 | kubectl delete configmap slice-components-configmap -n $NAMESPACE 9 | envsubst < manifests/deployment.yaml | kubectl delete -f - 10 | envsubst < manifests/service.yaml | kubectl delete -f - 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | requests 3 | numpy 4 | python-dotenv 5 | prometheus_client -------------------------------------------------------------------------------- /service_orchestrator/README.md: -------------------------------------------------------------------------------- 1 | Dummy Service Orchestrator 2 | ====================== 3 | This script provides a simple Service Orchestrator using Flask. 4 | It uses the Kubernetes API to get information about the NFs. 5 | 6 | Overview 7 | -------- 8 | - Intended to be run on a Kubernetes control plane node to avoid loading kubeconfig. 9 | - The monitoring manager component can make HTTP requests to this orchestrator to get infomation on slice components. 10 | - Real service Orchestrators (e.g., ONAP) will have more complex logic and additional APIs. -------------------------------------------------------------------------------- /service_orchestrator/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | SERVICE_NAME="service_orchestrator" 5 | USER="$(whoami)" 6 | WORKING_DIR="$(pwd)" 7 | SCRIPT_NAME="service-orchestrator.py" 8 | SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" 9 | PYTHON_PATH="$(which python3)" 10 | 11 | # Check if Python is installed 12 | if [ -z "$PYTHON_PATH" ]; then 13 | echo "Python3 is not installed. Please install it and try again." 14 | exit 1 15 | fi 16 | 17 | # Create the service file 18 | echo "Creating systemd service file at ${SERVICE_FILE}..." 19 | 20 | sudo bash -c "cat > ${SERVICE_FILE}" <", "get_slice_components", self.get_slice_components, methods=["GET"]) 58 | self.app.add_url_rule("/api/health", "check_health", self.check_health, methods=["GET"]) 59 | 60 | def get_slice_components(self, slice_id): 61 | if slice_id not in self.slice_info: 62 | self.logger.error(f"Slice ID {slice_id} not found in slice info.") 63 | return jsonify({"status": "error", "message": f"Slice ID {slice_id} not found"}), 404 64 | 65 | try: 66 | pods_info = self._get_pods_info() 67 | filtered_pods = self._filter_pods_by_slice_info(pods_info, self.slice_info[slice_id]) 68 | filtered_pods = self._filter_response({"pods": filtered_pods}) 69 | return jsonify({"status": "success", "pods": filtered_pods}), 200 70 | except Exception as e: 71 | self.logger.error(f"Error retrieving slice components: {str(e)}") 72 | return jsonify({"status": "error", "message": "Failed to retrieve slice components"}), 500 73 | 74 | def _get_pods_info(self): 75 | cmd = ["kubectl", "get", "pods", "-n", "open5gs", "-o", "json"] 76 | result = subprocess.run(cmd, capture_output=True, text=True) 77 | if result.returncode != 0: 78 | raise RuntimeError(f"kubectl command failed: {result.stderr}") 79 | pods_info = json.loads(result.stdout) 80 | return pods_info 81 | 82 | def _filter_response(self, response): 83 | filtered_response = [] 84 | pods = response.get("pods", []) 85 | for pod in pods: 86 | pod_info = {} 87 | metadata = pod.get("metadata", {}) 88 | name = metadata.get("name", "") 89 | nf = metadata.get("labels", {}).get("nf", "") 90 | pod_ip = pod.get("status", {}).get("podIP", "") 91 | 92 | pod_info["name"] = name 93 | pod_info["pod_ip"] = pod_ip 94 | pod_info["nss"] = "edge" 95 | pod_info["nf"] = nf 96 | filtered_response.append(pod_info) 97 | 98 | return filtered_response 99 | 100 | def _filter_pods_by_slice_info(self, pods_info, slice_pod_map): 101 | filtered_pods = [] 102 | for pod in pods_info["items"]: 103 | pod_labels = pod.get("metadata", {}).get("labels", {}) 104 | print(pod_labels) 105 | for item in slice_pod_map: 106 | name = item.get("nf") 107 | if name and pod_labels.get("name") == name: 108 | filtered_pods.append(pod) 109 | break 110 | 111 | self.logger.info(f"Filtered pods: {filtered_pods}") 112 | return filtered_pods 113 | 114 | def _load_slice_info(self, file_path): 115 | with open(file_path, "r") as file: 116 | slice_info = json.load(file) 117 | self.logger.info(f"Slice info loaded: {slice_info}") 118 | return slice_info 119 | 120 | def check_health(self): 121 | return jsonify({"status": "success", "message": "Service Orchestrator is healthy"}), 200 122 | 123 | def run(self, debug=False, port=5001, host="0.0.0.0"): 124 | self.app.run(debug=debug, port=port, host=host) 125 | 126 | 127 | if __name__ == "__main__": 128 | service_orchestrator = DummyServiceOrchestrator() 129 | service_orchestrator.run(debug=True, port=5001, host="0.0.0.0") 130 | -------------------------------------------------------------------------------- /service_orchestrator/slice_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "1-000001": [ 3 | { 4 | "nf": "upf1", 5 | "nss": "edge" 6 | }, 7 | { 8 | "nf": "smf1", 9 | "nss": "core" 10 | } 11 | ], 12 | "2-000002": [ 13 | { 14 | "nf": "upf2", 15 | "nss": "edge" 16 | }, 17 | { 18 | "nf": "smf2", 19 | "nss": "core" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /service_orchestrator/test-service-orchestrator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | print_subheader() { 4 | echo -e "\e[1;36m--- $1 ---\e[0m" 5 | } 6 | 7 | print_header() { 8 | echo -e "\n\e[1;34m############################### $1 ###############################\e[0m" 9 | } 10 | 11 | print_success() { 12 | echo -e "\e[1;32m$1\e[0m" 13 | } 14 | 15 | print_error() { 16 | echo -e "\e[1;31mERROR: $1\e[0m" 17 | } 18 | 19 | print_info() { 20 | echo -e "\e[1;33mINFO: $1\e[0m" 21 | } 22 | 23 | # Set base URL for the API 24 | BASE_URL="http://localhost:5001" 25 | 26 | # Perform health check 27 | print_header "Testing Service Orchestrator (External Component [1/2])" 28 | response=$(curl -s -w "%{http_code}" -o /dev/null $BASE_URL/api/health) 29 | 30 | if [ "$response" -eq 200 ]; then 31 | print_success "Health check passed! Service Orchestrator is healthy." 32 | else 33 | print_error "Health check failed with status code: $response. Please check the service logs for details." 34 | exit 1 35 | fi 36 | 37 | # Fetch slice information for slice 1-000001 and capture the response 38 | print_subheader "Fetching slice information for slice 1-000001" 39 | slice_info=$(curl -s $BASE_URL/slices/1-000001) 40 | 41 | # Check if the response contains valid data 42 | if [ -n "$slice_info" ]; then 43 | print_success "Successfully retrieved slice information for slice 1-000001." 44 | print_info "Slice Information: $slice_info" 45 | else 46 | print_error "Failed to fetch slice information for slice 1-000001." 47 | exit 1 48 | fi -------------------------------------------------------------------------------- /service_orchestrator/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SERVICE_NAME="service_orchestrator" 4 | SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" 5 | 6 | # Check if the service file exists 7 | if [ ! -f "$SERVICE_FILE" ]; then 8 | echo "Service file ${SERVICE_FILE} does not exist. Exiting." 9 | exit 1 10 | fi 11 | 12 | echo "Stopping the ${SERVICE_NAME} service..." 13 | sudo systemctl stop "$SERVICE_NAME" || { 14 | echo "Failed to stop the ${SERVICE_NAME} service. Exiting." 15 | exit 1 16 | } 17 | 18 | echo "Disabling the ${SERVICE_NAME} service..." 19 | sudo systemctl disable "$SERVICE_NAME" || { 20 | echo "Failed to disable the ${SERVICE_NAME} service. Exiting." 21 | exit 1 22 | } 23 | 24 | echo "Removing the service file at ${SERVICE_FILE}..." 25 | sudo rm "$SERVICE_FILE" || { 26 | echo "Failed to remove the service file. Exiting." 27 | exit 1 28 | } 29 | 30 | echo "Reloading systemd daemon to apply changes..." 31 | sudo systemctl daemon-reload 32 | 33 | # Optional: Clean up logs 34 | echo "Clearing systemd logs for ${SERVICE_NAME}..." 35 | sudo journalctl --vacuum-files=1 -u "$SERVICE_NAME" &> /dev/null 36 | 37 | # Verify removal 38 | if ! systemctl status "$SERVICE_NAME" &> /dev/null; then 39 | echo "${SERVICE_NAME} service has been successfully removed." 40 | else 41 | echo "Warning: ${SERVICE_NAME} service may still be present." 42 | fi -------------------------------------------------------------------------------- /service_orchestrator/view-logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo journalctl -u service_orchestrator -f -------------------------------------------------------------------------------- /slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysh/5g-monarch/8d5fe9a618f4e603c4877d8cb05f5d8f597cbc8e/slides.pdf -------------------------------------------------------------------------------- /test-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./test-external.sh 3 | ./test-monarch-core.sh 4 | ./test-monarch-nss.sh -------------------------------------------------------------------------------- /test-external.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NAMESPACE="monarch" 3 | USER="$(whoami)" 4 | WORKING_DIR="$(pwd)" 5 | 6 | cd $WORKING_DIR/service_orchestrator 7 | ./test-service-orchestrator.sh 8 | 9 | cd $WORKING_DIR/nfv_orchestrator 10 | ./test-nfv-orchestrator.sh -------------------------------------------------------------------------------- /test-monarch-core.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NAMESPACE="monarch" 3 | USER="$(whoami)" 4 | WORKING_DIR="$(pwd)" 5 | 6 | cd $WORKING_DIR/data_store 7 | ./test-data-store.sh 8 | 9 | cd $WORKING_DIR/data_distribution 10 | ./test-data-distribution.sh 11 | 12 | cd $WORKING_DIR/data_visualization 13 | ./test-data-visualization.sh 14 | 15 | cd $WORKING_DIR/monitoring_manager 16 | ./test-monitoring-manager.sh 17 | 18 | cd $WORKING_DIR/request_translator 19 | ./test-request-translator.sh -------------------------------------------------------------------------------- /test-monarch-nss.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NAMESPACE="monarch" 3 | USER="$(whoami)" 4 | WORKING_DIR="$(pwd)" 5 | 6 | cd $WORKING_DIR/nssdc 7 | ./test-nssdc.sh -------------------------------------------------------------------------------- /utils/cecho.sh: -------------------------------------------------------------------------------- 1 | # Based on https://stackoverflow.com/a/53463162/9346339 2 | cecho(){ 3 | RED="\033[0;31m" 4 | GREEN="\033[0;32m" # <-- [0 means not bold 5 | YELLOW="\033[1;33m" # <-- [1 means bold 6 | CYAN="\033[1;36m" 7 | # ... Add more colors if you like 8 | 9 | NC="\033[0m" # No Color 10 | 11 | # printf "${(P)1}${2} ${NC}\n" # <-- zsh 12 | printf "${!1}${2} ${NC}\n" # <-- bash 13 | } -------------------------------------------------------------------------------- /utils/check_status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source ../utils/cecho.sh 3 | check_status() { 4 | component="$1" 5 | namespace="$2" 6 | 7 | if [ "$(helm status "$component" -n "$namespace" -o json 2>/dev/null | jq -r '.info.status')" = "deployed" ]; then 8 | cecho "GREEN" "Component $component is deployed." 9 | return 10 | fi 11 | cecho "RED" "Component $component is not deployed." 12 | 13 | 14 | } -------------------------------------------------------------------------------- /utils/ubuntu-sleep.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: ubuntu 5 | labels: 6 | app: ubuntu 7 | spec: 8 | containers: 9 | - image: ubuntu 10 | command: 11 | - "sleep" 12 | - "604800" 13 | imagePullPolicy: IfNotPresent 14 | name: ubuntu 15 | resources: 16 | requests: 17 | memory: "100Mi" 18 | cpu: "100m" 19 | limits: 20 | memory: "200Mi" 21 | cpu: "200m" 22 | restartPolicy: Always 23 | --------------------------------------------------------------------------------