Add SME Client to 5G Slice PRB Prediction Rapp 74/15174/2
authorsunil.n <sunil.n@samsung.com>
Fri, 24 Oct 2025 06:28:52 +0000 (11:58 +0530)
committersunil.n <sunil.n@samsung.com>
Thu, 30 Oct 2025 04:05:28 +0000 (09:35 +0530)
Change-Id: I2ffd236189351d3a536b242852ea2364e602ef79
Signed-off-by: sunil.n <sunil.n@samsung.com>
Change-Id: Iabd2d60f902b9a00cf99e586db789bff0692d1e8
Signed-off-by: sunil.n <sunil.n@samsung.com>
Update README.md - sme_client.py information

Change-Id: I72747badaf185b7d2fe3bd57b1d8211ad795cc32
Signed-off-by: sunil.n <sunil.n@samsung.com>
sample-rapp-generator/rapp-slice-prb-prediction/README.md
sample-rapp-generator/rapp-slice-prb-prediction/src/config.json [new file with mode: 0644]
sample-rapp-generator/rapp-slice-prb-prediction/src/models/README.md [new file with mode: 0644]
sample-rapp-generator/rapp-slice-prb-prediction/src/sme_client.py [new file with mode: 0644]

index b1b3a7b..622ddf2 100644 (file)
@@ -5,6 +5,9 @@ Using 5G RAN Slice PRB Prediction Rapp, we can properly manage available RAN res
 ## Directory Structure
 
 - `src` - contains source code for the Rapp
+  - `config.json` - configuration file for database, RAPP, and SME settings
+  - `sme_client.py` - Service Management Environment client for service discovery
+  - `models/` - directory for trained AI/ML models and scalers
 - `data_generator.py` - generates data for training and testing the model
 - `Dockerfile` - contains instructions to build a Docker image for the Rapp
 
@@ -78,3 +81,109 @@ Each slice has unique identifiers and generates KPIs based on its specific traff
 4. **Database Storage**: Writes data to InfluxDB using batch processing for efficiency
 
 This generated data provides a comprehensive dataset for training the PRB prediction model with realistic network slice behavior patterns.
+
+## Configuration (`src/config.json`)
+
+The `config.json` file contains all necessary configuration parameters for the RAPP operation, organized into three main sections:
+
+### Database Configuration (DB)
+```json
+{
+  "DB": {
+    "address": "http://localhost:8086",           // InfluxDB server URL
+    "token": "",                                  // InfluxDB authentication token
+    "org": "",                                    // InfluxDB organization name
+    "bucket": "nssi_pm_bucket",                   // Database bucket name
+    "time_range": "-0",                           // Time range for queries
+    "measurements": "nssi_pm_bucket",             // Measurement name
+    "window_size": 672,                           // Data window size for model
+    "field_names": [...],                         // KPI field names to monitor
+    "tag_slice_type": "sliceType",                // Tag for slice type filtering
+    "tag_nssi_id": "measObjLdn"                   // Tag for NSSI identification
+  }
+}
+```
+
+### RAPP Configuration
+```json
+{
+  "RAPP": {
+    "interval": "1",                              // Processing interval
+    "ran_nssmf_address": "http://localhost:8080", // RAN NSSMF endpoint
+    "callback_uri": "http://localhost:8080/handleFileReadyNotification"
+  }
+}
+```
+
+### Service Management Environment (SME)
+```json
+{
+  "SME": {
+    "sme_discovery_endpoint": "http://localhost:31575/service-apis/v1/allServiceAPIs",
+    "invoker_id": "6a965002-ed7c-4f69-855c-ab9196f86e61",
+    "influxdb_api_name": "influxdb2-http",
+    "influxdb_resource_name": "root",
+    "ran_nssmf_api_name": "",
+    "ran_nssmf_resource_name": ""
+  }
+}
+```
+
+## Service Discovery (`src/sme_client.py`)
+
+The `sme_client.py` module implements a Service Management Environment (SME) client that enables dynamic service discovery in the O-RAN ecosystem. This component allows the RAPP to locate and connect to available services without hardcoding endpoint information.
+
+### Key Features
+
+- **Dynamic Service Discovery**: Automatically discovers service endpoints by querying the SME
+- **O-RAN Compliance**: Follows O-RAN Service-Based Architecture (SBA) principles
+- **Flexible Resource Access**: Supports discovery of specific API resources within services
+- **Error Handling**: Robust error handling for network and parsing issues
+
+### SMEClient Class
+
+The main class provides the following functionality:
+
+#### Initialization
+```python
+client = SMEClient(invoker_id, api_name, resource_name)
+```
+
+**Parameters:**
+- `invoker_id`: Unique identifier for the service consumer
+- `api_name`: Name of the API service to discover
+- `resource_name`: Specific resource within the API to access
+
+#### Service Discovery Process
+
+1. **Query Construction**: Builds discovery query with API invoker ID and API name
+2. **SME Request**: Sends HTTP GET request to SME discovery endpoint
+3. **Response Parsing**: Extracts service information from nested JSON response
+4. **URL Construction**: Builds complete HTTP URL for service access
+
+#### Usage Example
+```python
+# Initialize SME client for InfluxDB service
+sme_client = SMEClient(
+    invoker_id="6a965002-ed7c-4f69-855c-ab9196f86e61",
+    api_name="influxdb2-http",
+    resource_name="root"
+)
+
+# Discover service endpoint
+service_url = sme_client.discover_service()
+if service_url:
+    print(f"Discovered service at: {service_url}")
+    # Use the service URL for API calls
+else:
+    print("Service discovery failed")
+```
+
+### SME Response Structure
+
+The SME returns a nested JSON structure containing:
+- `serviceAPIDescriptions`: Array of available services
+- `aefProfiles`: Application Exposure Function profiles
+- `versions`: API version information
+- `resources`: Available resources within each version
+- `interfaceDescriptions`: Network endpoint details (IP, port)
\ No newline at end of file
diff --git a/sample-rapp-generator/rapp-slice-prb-prediction/src/config.json b/sample-rapp-generator/rapp-slice-prb-prediction/src/config.json
new file mode 100644 (file)
index 0000000..9f467a0
--- /dev/null
@@ -0,0 +1,27 @@
+{
+  "DB": {
+    "address": "http://localhost:8086",
+    "token": "",
+    "org": "",
+    "bucket": "nssi_pm_bucket",
+    "time_range": "-0",
+    "measurements": "nssi_pm_bucket",
+    "window_size" : 672,
+    "field_names": ["RRU.PrbDl.SNSSAI", "DRB.PdcpSduVolumeDL.SNSSAI", "RRC.ConnEstabSucc.Cause"],
+    "tag_slice_type": "sliceType",
+    "tag_nssi_id": "measObjLdn"
+  },
+  "RAPP": {
+    "interval": "1",
+    "ran_nssmf_address": "http://localhost:8080",
+    "callback_uri": "http://localhost:8080/handleFileReadyNotification"
+  },
+  "SME": {
+    "sme_discovery_endpoint": "http://localhost:31575/service-apis/v1/allServiceAPIs",
+    "invoker_id": "6a965002-ed7c-4f69-855c-ab9196f86e61",
+    "influxdb_api_name": "influxdb2-http",
+    "influxdb_resource_name": "root",
+    "ran_nssmf_api_name": "",
+    "ran_nssmf_resource_name": ""
+  }
+}
\ No newline at end of file
diff --git a/sample-rapp-generator/rapp-slice-prb-prediction/src/models/README.md b/sample-rapp-generator/rapp-slice-prb-prediction/src/models/README.md
new file mode 100644 (file)
index 0000000..a83d717
--- /dev/null
@@ -0,0 +1 @@
+models folder contains trained AI/ML models and scalers.
\ No newline at end of file
diff --git a/sample-rapp-generator/rapp-slice-prb-prediction/src/sme_client.py b/sample-rapp-generator/rapp-slice-prb-prediction/src/sme_client.py
new file mode 100644 (file)
index 0000000..f662563
--- /dev/null
@@ -0,0 +1,164 @@
+"""
+Service Management Environment (SME) Client
+
+This module implements a client for discovering services in an O-RAN Service Management Environment.
+The SME provides service discovery capabilities that allow RAPP applications to dynamically locate
+and connect to available services without hardcoding endpoint information.
+"""
+
+import requests
+import logging
+import json
+
+# Configure logger for this module
+logger = logging.getLogger(__name__)
+
+class SMEClient:
+    """
+    Service Management Environment Client for O-RAN service discovery.
+    
+    This client enables RAPP applications to dynamically discover and connect to services
+    in an O-RAN ecosystem by querying the SME (Service Management Environment) for
+    service endpoint information.
+    
+    Attributes:
+        invoker_id (str): Identifier for the API invoker/service consumer
+        api_name (str): Name of the API to discover
+        resource_name (str): Specific resource name within the API
+        sme_discovery_endpoint (str): URL endpoint for SME service discovery
+    """
+    
+    def __init__(self, invoker_id, api_name, resource_name):
+        """
+        Initialize the SME Client with service discovery parameters.
+        
+        Args:
+            invoker_id (str): Unique identifier for the service consumer
+            api_name (str): Name of the API service to discover
+            resource_name (str): Specific resource within the API to access
+            
+        Raises:
+            FileNotFoundError: If config.json file is not found
+            json.JSONDecodeError: If config.json contains invalid JSON
+            KeyError: If required SME configuration is missing
+        """
+        # Store service identification parameters
+        self.invoker_id = invoker_id
+        self.api_name = api_name
+        self.resource_name = resource_name
+
+        # Load SME configuration from config.json file
+        with open('config.json', 'r') as f:
+            config = json.load(f)
+
+        # Extract SME discovery endpoint from configuration
+        sme_config = config.get("SME", {})
+        self.sme_discovery_endpoint = sme_config.get("sme_discovery_endpoint")
+
+    def discover_service(self):
+        """
+        Discover service endpoint by querying the SME discovery service.
+        
+        This method constructs a query with the API invoker ID and API name,
+        sends it to the SME discovery endpoint, and parses the response
+        to extract the service URI.
+        
+        Returns:
+            str: Complete HTTP URL for the discovered service, or None if discovery fails
+            
+        Raises:
+            requests.RequestException: If network request fails (handled internally)
+        """
+        # Construct query parameters for service discovery
+        query = f"api-invoker-id=api_invoker_id_{self.invoker_id}&api-name={self.api_name}"
+        full_url = f"{self.sme_discovery_endpoint}?{query}"
+        logger.info(f"Full URL for service discovery: {full_url}")
+
+        try:
+            # Make HTTP GET request to SME discovery endpoint
+            response = requests.get(full_url, headers={"Content-Type": "application/json"})
+            
+            if response.status_code == 200:
+                logger.info("Service discovery successful.")
+                # Parse the JSON response to extract service URI
+                return self.parse_uri(response.json())
+            else:
+                # Log error details for failed requests
+                logger.error(f"Failed to discover service. Status code: {response.status_code}")
+                logger.error(response.text)
+                return None
+        except requests.RequestException as e:
+            # Handle network-related errors (connection timeout, DNS resolution, etc.)
+            logger.error(f"Error during service discovery: {e}")
+            return None
+
+    def parse_uri(self, response):
+        """
+        Parse the SME service discovery response to extract the service URI.
+        
+        The SME response follows a nested structure containing service descriptions,
+        AEF (Application Exposure Function) profiles, versions, and resources.
+        This method navigates through this structure to find the specific resource
+        and construct the complete service URL.
+        
+        Args:
+            response (dict): JSON response from SME service discovery
+            
+        Returns:
+            str: Complete HTTP URL for the service resource, or None if parsing fails
+            
+        Expected Response Structure:
+            {
+                "serviceAPIDescriptions": [
+                    {
+                        "aefProfiles": [
+                            {
+                                "versions": [
+                                    {
+                                        "resources": [
+                                            {
+                                                "resourceName": "resource_name",
+                                                "uri": "/api/endpoint"
+                                            }
+                                        ]
+                                    }
+                                ],
+                                "interfaceDescriptions": [
+                                    {
+                                        "ipv4Addr": "",
+                                        "port": 0
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                ]
+            }
+        """
+        try:
+            logger.debug("Parsing SME response to extract URI.")
+            
+            # Navigate through the nested response structure
+            service = response["serviceAPIDescriptions"][0]  # Get first service description
+            profile = service["aefProfiles"][0]              # Get first AEF profile
+            version = profile["versions"][0]                 # Get first API version
+            
+            # Find the specific resource by name within the version's resources
+            resource = next(
+                (res for res in version["resources"] if res["resourceName"] == self.resource_name),
+                None
+            )
+            uri = resource["uri"] if resource else None      # Extract resource URI if found
+
+            # Extract network interface information
+            interface = profile["interfaceDescriptions"][0]  # Get first interface description
+            ipv4_addr = interface.get("ipv4Addr")           # Get IPv4 address
+            port = interface.get("port")                     # Get port number
+
+            # Construct complete URL: http://ipv4:port/uri (or just http://ipv4:port if no URI)
+            return f"http://{ipv4_addr}:{port}{uri}" if uri else f"http://{ipv4_addr}:{port}"
+            
+        except (KeyError, IndexError, TypeError) as e:
+            # Handle various parsing errors (missing keys, empty lists, wrong data types)
+            logger.error(f"Error parsing URI: {e}")
+            return None