For a complete guide on the installation of the SMO rApp platform,
please follow the instructions [here](https://gerrit.o-ran-sc.org/r/gitweb?p=it/dep.git;a=blob_plain;f=smo-install/README.md;hb=HEAD).
+As an additional note, a special flavour of the SMO installation is available for the Energy Saving rApp demo.
+This flavour is located in the `smo-install/helm-override/ranpm-pynts-es-rapp` directory.
+There is some detail on flavours [here](https://github.com/o-ran-sc/it-dep/blob/master/smo-install/README.md).
+This flavour is designed to install the SMO components required for the Energy Saving rapp demo.
+After all the other steps in the SMO installation guide are completed, you can run the following command to
+install the Energy Saving rApp flavour:
+
+```bash
+./dep/smo-install/scripts/layer-2/2-install-oran.sh ranpm-pynts-es-rapp dev
+```
+
+Once all the components are installed and ready, you can proceed with the install of the DU simulators.
+These will start a flow of sample data through the system, which will be used by the Energy Saving rApp demo.
+wait around 10 minutes after all the components are installed before proceeding with the next simulator install.
+
+```bash
+./dep/smo-install/scripts/layer-2/2-install-simulators.sh ranpm-pynts-es-rapp
+```
+
+```text
+NOTE: The installation is just pointed at with the above commands. For the full installation and details of flavours, go to the smo installation docs.
+```
+[SMO Install Guide](https://github.com/o-ran-sc/it-dep/blob/master/smo-install/README.md)
+
### Energy Saving rApp Deployment Preparation
1. The rApp needs to know the address and port of your chart repository. We can do this by running the following command:
```bash
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 0.2.5
+version: 0.2.16
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
-appVersion: "0.2.5"
+appVersion: "0.2.16"
"ncmp_api_name": "{{ .Values.ncmp.apiName }}",
"ncmp_resource_name": "{{ .Values.ncmp.resourceName }}",
"resource_id": "{{ .Values.ncmp.resourceId}}",
+ "ncmp_managed_element_id": "{{ .Values.ncmp.managedElementId }}",
+ "ncmp_gnbdufunction_id": "{{ .Values.ncmp.gnbduFunctionId }}",
"influxdb_invoker_id": "{{ .Values.environment.appId }}",
"influxdb_api_name": "{{ .Values.influxdb.apiName }}",
"influxdb_resource_name": "{{ .Values.influxdb.resourceName }}",
value: {{ .Values.environment.appId | quote }}
- name: SME_DISCOVERY_ENDPOINT
value: {{ .Values.environment.smeDiscoveryEndpoint | quote }}
+ - name: INFLUX_TOKEN
+ valueFrom:
+ secretKeyRef:
+ key: token
+ name: {{ .Values.influxdb.tokenSecretName | quote }}
volumeMounts:
- mountPath: /app/config.json
name: config
repository: "nexus3.onap.org:10001/estdemoimages/es-rapp"
pullPolicy: Always
# Overrides the image tag whose default is the chart appVersion.
- tag: "0.2.5"
+ tag: "0.2.16"
imagePullSecrets: []
nameOverride: "energy-saving-rapp"
influxdb:
token: "hIVMgY7vn3M772PSk3yrI2IjeWzybPw0"
+ # This token secret is part of the smo install. The rapp will expect that this secret is created in the same namespace as the rapp.
+ tokenSecretName: influxdb-api-token
user: "admin"
password: "mySuP3rS3cr3tT0keN"
bucket: "pm-logg-bucket"
org: "est"
apiName: "influxdb2-http"
resourceName: "root"
- timeRange: "-6h"
+ timeRange: "-5m"
measurements:
- "ManagedElement=o-du-pynts-1122,ManagedElement=o-du-pynts-1122,GNBDUFunction=1,NRCellDU=1"
- "ManagedElement=o-du-pynts-1123,ManagedElement=o-du-pynts-1123,GNBDUFunction=1,NRCellDU=1"
ncmp:
- apiName: "ncmp-dmi-plugin-http"
+ apiName: "cps-core-http"
resourceName: "root"
resourceId: "/_3gpp-common-managed-element:ManagedElement=ManagedElement-002/_3gpp-nr-nrm-gnbdufunction:GNBDUFunction=GNBDUFunction-001/_3gpp-nr-nrm-nrcelldu:NRCellDU=NRCellDU-001/attributes"
+ managedElementId: "ManagedElement-002"
+ gnbduFunctionId: "GNBDUFunction-001"
kserve:
apiName: "es-predictor-http"
teiv:
apiName: "topology-exposure-http"
resourceName: "root"
- oduFunctionId: "GNBDUFunction-001"
+ oduFunctionId: "urn:oran:smo:teiv:GNBDUFunction-001"
appStartup:
command: ["/bin/sh", "-c"]
artifacts:\r
energy-saving:\r
type: tosca.artifacts.asd.deploymentItem\r
- file: "Artifacts/Deployment/HELM/energy-saving-rapp-0.2.5.tgz"\r
+ file: "Artifacts/Deployment/HELM/energy-saving-rapp-0.2.16.tgz"\r
properties:\r
artifact_type: "helm_chart"\r
target_server: "chartmuseum"\r
"chart": {
"chartId": {
"name": "energy-saving-rapp",
- "version": "0.2.5"
+ "version": "0.2.16"
},
- "namespace": "nonrtric",
+ "namespace": "smo",
"releaseName": "energy-saving-rapp",
"podName": "energy-saving-rapp",
"repository": {
"host": "localhost",
"port": 31575,
"ncmp_invoker_id": "6a965002-ed7c-4f69-855c-ab9196f86e61",
- "ncmp_api_name": "ncmp-dmi-plugin-http",
+ "ncmp_api_name": "cps-core-http",
"ncmp_resource_name": "root",
"resource_id": "/_3gpp-common-managed-element:ManagedElement=ManagedElement-002/_3gpp-nr-nrm-gnbdufunction:GNBDUFunction=GNBDUFunction-001/_3gpp-nr-nrm-nrcelldu:NRCellDU=NRCellDU-001/attributes",
+ "ncmp_managed_element_id": "ManagedElement-002",
+ "ncmp_gnbdufunction_id": "GNBDUFunction-001",
"influxdb_invoker_id": "6a965002-ed7c-4f69-855c-ab9196f86e61",
"influxdb_api_name": "influxdb2-http",
"influxdb_resource_name": "root",
"teiv_invoker_id": "6a965002-ed7c-4f69-855c-ab9196f86e61",
"teiv_api_name": "topology-exposure-http",
"teiv_resource_name": "root",
- "odufunction_id": "GNBDUFunction-001"
+ "odufunction_id": "urn:oran:smo:teiv:GNBDUFunction-001"
},
"DB": {
"host": "10.101.3.89",
# limitations under the License.
# ============LICENSE_END=================================================
#
-
+import os
import time
import logging
from influxdb.exceptions import InfluxDBClientError, InfluxDBServerError
time.sleep(60)
def mapping(self, data):
- data[['S', 'B', 'C']] = data['CellID'].str.extract(r'S(\d+)/[BN](\d+)/C(\d+)')
+ data[['S', 'B', 'C']] = data['CellID'].str.extract(r'S(\d+)-[BN](\d+)-C(\d+)')
data[['S', 'B', 'C']] = data[['S', 'B', 'C']].astype(int)
data = data.sort_values(by=['B', 'S', 'C'])
data['cellidnumber'] = data.groupby(['B', 'S', 'C']).ngroup().add(1)
for field in fields:
value = (
- f"S{random.randint(1,9)}/B{random.randint(1,9)}/C{random.randint(1,9)}" if field == "CellID"
+ f"S{random.randint(1,9)}-B{random.randint(1,9)}-C{random.randint(1,9)}" if field == "CellID"
else (900 if field == "GranularityPeriod"
else str(round(random.uniform(1, 100), 5)))
)
# Initialize the InfluxDB client
influx_config = config.get("DB", {})
- self.token = influx_config.get("token")
+
+ if os.getenv('INFLUX_TOKEN'):
+ self.token = os.getenv('INFLUX_TOKEN')
+ else:
+ logger.info("INFLUX_TOKEN environment variable is not set.")
+ self.token = influx_config.get("token")
+
self.org = influx_config.get("org")
self.bucket = influx_config.get("bucket")
self.address = influx_config.get("address")
status_code, response_text = self.assist.send_request_to_server(json_data, randomize=self.random_predictions)
if not self.check_and_perform_action(response_text):
cell_id_name = group_data['CellID'].iloc[0]
+ # Check if the cell is in TEIV
+ self.check_cell_in_teiv(cell_id_name)
du_name = self.extract_managed_element(group_data['_measurement'].iloc[0])
- full_cell_id = cell_id_name + "-" + du_name
+ cell_with_node = cell_id_name + "_" + du_name
logger.info(f"Turn on the cell {group_name}")
# Wait for 3 seconds before performing the action
time.sleep(3)
- if full_cell_id not in self.cell_power_status:
- logger.debug(f"Cell {full_cell_id} not in local cache. Adding it...")
- self.cell_power_status[full_cell_id] = "off"
+ if cell_with_node not in self.cell_power_status:
+ logger.debug(f"Cell {cell_with_node} not in local cache. Adding it...")
+ self.cell_power_status[cell_with_node] = "off"
# Check if the cell is already powered on
- if self.cell_power_status[full_cell_id] == "on":
- logger.debug(f"Cell {full_cell_id} is already powered on.")
+ if self.cell_power_status[cell_with_node] == "on":
+ logger.debug(f"Cell {cell_with_node} is already powered on.")
# continue
else:
- self.ncmp_client.power_on_cell(full_cell_id)
- self.cell_power_status[full_cell_id] = "on"
+ self.ncmp_client.power_on_cell(cell_with_node)
+ self.cell_power_status[cell_with_node] = "on"
else:
du_name = self.extract_managed_element(group_data['_measurement'].iloc[0])
cell_id_name = group_data['CellID'].iloc[0]
- full_cell_id = cell_id_name + "-" + du_name
+ # Check if the cell is in TEIV
+ self.check_cell_in_teiv(cell_id_name)
+ cell_with_node = cell_id_name + "_" + du_name
logger.info(f"Turn off the cell {group_name}")
# Wait for 3 seconds before performing the action
time.sleep(3)
- if full_cell_id not in self.cell_power_status:
- logger.debug(f"Cell {full_cell_id} not in local cache. Adding it...")
- self.cell_power_status[full_cell_id] = "on"
+ if cell_with_node not in self.cell_power_status:
+ logger.debug(f"Cell {cell_with_node} not in local cache. Adding it...")
+ self.cell_power_status[cell_with_node] = "on"
- if self.cell_power_status[full_cell_id] == "off":
- logger.debug(f"Cell {full_cell_id} is already powered off.")
+ if self.cell_power_status[cell_with_node] == "off":
+ logger.debug(f"Cell {cell_with_node} is already powered off.")
# continue
else:
- self.ncmp_client.power_off_cell(full_cell_id)
- self.cell_power_status[full_cell_id] = "off"
+ if self.ncmp_client.power_off_cell(cell_with_node):
+ self.cell_power_status[cell_with_node] = "off"
def extract_managed_element(self, measurement):
if '=' not in measurement or ',' not in measurement:
def mapping(self, data):
data = pd.DataFrame(data)
# TODO: This regex is not likely to match all cell IDs. Will need to be improved.
- data[['S', 'B', 'C']] = data['CellID'].str.extract(r'S(\d+)/[BN](\d+)/C(\d+)')
+ data[['S', 'B', 'C']] = data['CellID'].str.extract(r'S(\d+)-[BN](\d+)-C(\d+)')
data[['S', 'B', 'C']] = data[['S', 'B', 'C']].astype(int)
data = data.sort_values(by=['B', 'S', 'C'])
data['cellidnumber'] = data.groupby(['B', 'S', 'C']).ngroup().add(1)
return False
return False
+ # Check if the cell is in TEIV cell inventory
+ def check_cell_in_teiv(self, cell_id):
+ # Check if the cell ID is in the TEIV cell inventory
+ self.teiv_cells = self.teiv_client.get_nrcelldus()
-
- # def get_teiv_cells(self):
- # # Get the TEIV cells from the teiv
- # odufunction_id = self.teiv_client.odufunction_id
- # logger.info("ODU function ID: " + str(odufunction_id))
- # # Get the NRCellDUs from the TEIV
- # nrcelldus = self.teiv_client.get_nrcelldus(odufunction_id)
- # if nrcelldus is None:
- # logger.error("Failed to retrieve NRCellDUs.")
- # return None
- # # Extract the cell IDs from the NRCellDUs
- # self.teiv_cells = self.teiv_client.search_entity_data_for_ids(nrcelldus)
- # logger.info("NRCellDUs: " + str(self.nrcelldus))
+ if cell_id in self.teiv_cells:
+ logger.info(f"Cell {cell_id} is in the TEIV cell inventory.")
+ else:
+ logger.info(f"Cell {cell_id} is not in the TEIV cell inventory.")
if __name__ == "__main__":
import json
import logging
+import requests
from sme_client import SMEClient
logger = logging.getLogger(__name__)
self.ncmp_invoker_id = sme_config.get("ncmp_invoker_id")
self.ncmp_api_name = sme_config.get("ncmp_api_name")
self.ncmp_resource_name = sme_config.get("ncmp_resource_name")
+ self.ncmp_me = sme_config.get("ncmp_managed_element_id", "ManagedElement-002")
+ self.ncmp_gnb = sme_config.get("ncmp_gnbdufunction_id", "GNBDUFunction-001")
self.resourse_identifier = sme_config.get("resource_id")
self.ncmp_uri = None
print("Discovered NCMP URI: ", self.ncmp_uri)
- def power_off_cell(self, endpoint):
+ def power_off_cell(self, cell_with_node):
+ passthrough_request = self.make_passthrough_request(cell_with_node)
# This log is all it does in testing
- logger.info("Powering-off cell " + str(endpoint) + " successful")
+ logger.info("Powering-off cell " + str(cell_with_node) + " in progress...")
# It expects the SME ncmp endpoint to call power off
# endpoint_with_query = f"{endpoint}?resourceIdentifier={self.resourse_identifier}"
#
- # headers = {
- # "Content-Type": "application/json"
- # }
- #
- # body = {
- # "attributes": {
- # "administrativeState": "LOCKED"
- # }
- # }
- #
- # response = requests.patch(endpoint_with_query, data=body, headers=headers)
- #
- # if response.status_code == 200:
- # logger.info("Power-off successful. " + response.text)
- # return response.json()
- # else:
- # logger.error(f"Error in connection to NCMP for power off: {response.status_code}")
- # logger.error(response.text)
- # return None
+ headers = {
+ "Content-Type": "application/json"
+ }
+
+ body = {
+ "attributes": {
+ "administrativeState": "LOCKED"
+ }
+ }
+
+ response = requests.patch(passthrough_request, json=body, headers=headers)
- def power_on_cell(self, endpoint):
+ if response.status_code == 200:
+ logger.info("Power-off successful. " + response.text)
+ return True
+ else:
+ logger.error(f"Error in connection to NCMP for power off: {response.status_code}")
+ logger.error(response.text)
+ return False
+
+ def power_on_cell(self, cell_with_node):
# This log is all it does in testing
- logger.info("Powering-on cell " + str(endpoint) + " successful")
+ passthrough_request = self.make_passthrough_request(cell_with_node)
+ logger.info("Powering-on cell " + str(cell_with_node) + " in progress...")
# It expects the SME ncmp endpoint to call power on
# endpoint_with_query = f"{endpoint}?resourceIdentifier={self.resourse_identifier}"
- #
- # headers = {
- # "Content-Type": "application/json"
- # }
- #
- # body = {
- # "attributes": {
- # "administrativeState": "UNLOCKED"
- # }
- # }
- #
- # response = requests.patch(endpoint_with_query, data=body, headers=headers)
- #
- # if response.status_code == 200:
- # logger.info("Power-on successful. " + response.text)
- # return response.json()
- # else:
- # logger.error(f"Error in connection to NCMP for power on: {response.status_code}")
- # logger.error(response.text)
- # return None
+
+ headers = {
+ "Content-Type": "application/json"
+ }
+
+ body = {
+ "attributes": {
+ "administrativeState": "UNLOCKED"
+ }
+ }
+
+ response = requests.patch(passthrough_request, json=body, headers=headers)
+
+ if response.status_code == 200:
+ logger.info("Power-on successful. " + response.text)
+ return True
+ else:
+ logger.error(f"Error in connection to NCMP for power on: {response.status_code}")
+ logger.error(response.text)
+ return False
+
+ def make_passthrough_request(self, cell_with_node):
+ node_id = cell_with_node.split('_')[1]
+ cell_id = cell_with_node.split('_')[0]
+
+ endpoint = f"ncmp/v1/ch/{node_id}/data/ds/ncmp-datastore%3Apassthrough-running"
+ query_param = (f"?resourceIdentifier=/_3gpp-common-managed-element:ManagedElement={self.ncmp_me}"
+ f"/_3gpp-nr-nrm-gnbdufunction:GNBDUFunction={self.ncmp_gnb}"
+ f"/_3gpp-nr-nrm-nrcelldu:NRCellDU={cell_id}/attributes")
+
+ return f"{self.ncmp_uri}{endpoint}{query_param}"
# if __name__ == "__main__":
# logging.basicConfig(level=logging.INFO) # Set up logging for better visibility
resource_name=self.teiv_resource_name
)
- self.teiv_uri = sme_client.discover_service()
+ self.teiv_uri = sme_client.discover_service() + "topology-inventory/v1alpha11/"
print("Discovered TEIV URI: ", self.teiv_uri)
- def get_nrcelldus(self, odufunction_id):
-
+ def get_nrcelldus(self):
+ odufunction_id = self.odufunction_id
scope_filter = f"/provided-by-oduFunction[@id=\"{odufunction_id}\"]"
encoded_scope_filter = urllib.parse.quote(scope_filter)
endpoint = (
response = requests.get(endpoint)
if response.status_code == 200:
- logger.info("Retrieved NRCellDUs. " + response.text)
- return response.json()
+ nrcelldu_ids = self.search_entity_data_for_ids(response.json())
+ logger.info(f"Retrieved NRCellDU IDs form TEIV: {nrcelldu_ids}")
+ return nrcelldu_ids
else:
logger.error(f"Error in connection to TEIV: {response.status_code}")
logger.error(response.text)
ids = []
for item in items:
for key in item:
- ids.extend(entity.get('id') for entity in item[key] if 'id' in entity)
+ ids.extend(
+ entity.get('id').split(':')[-1]
+ for entity in item[key]
+ if 'id' in entity
+ )
return ids
# if __name__ == "__main__":
# # Instantiate the TEIVClient
# teiv_client = TEIV_CLIENT()
#
-# nrcelldu_json = teiv_client.get_nrcelldus(teiv_client.odufunction_id)
-# nrcelldu_ids = teiv_client.search_entity_data_for_ids(nrcelldu_json)
\ No newline at end of file
+# nrcelldu_json = teiv_client.get_nrcelldus()