-# RIC end-to-end (E2E) deployment and testing:
-1. Clone the it_dep and it_test folders on your VM
-2. Install kubernetes using the script in it_dep_kubernetes/setup-1node-k8s.sh
-3. Install the RIC using the script in it_dep_ricplt/ric_install.sh
-4. Copy robot_install.sh from it_test_ric_robot_suite_helm_ into it_dep_ricplt/
-5. cd into it_dep_ricplt and run robot_install.sh. It will create a softlink to the it_test_ric_robot_suite helm director and do the helm install of rig-robot from it_dep_ricplt
-6. The command “kubectl -n ricplatform get pods” will show the additional pod for ric-robot.
-7. cd into it_test_ric_robot_suite_helm_ric-robot and run ./ete.sh health to verify that the RIC is ready and deployed.
+This repository contains tools for end-to-end testing of the RIC. Among the included tools:
+
+o In simulators/, an implementation of the gNodeB E2 interface to allow
+testing without a live gNodeB and a workload generator to exercise the
+interfaces between XApps
+o In ric_robot_suite/, implementations of the Robot Framework
+(robotframework.org) to enable end-to-end test automation for
+development and integration testing.
+
+These components generally assume a deployed and running RIC.
+
+See the component subdirectories for documentation on the components themselves.
--- /dev/null
+This directory contains containers, Helm charts, libraries, and
+testsuites to support automated, end-to-end testing of the RIC.
+Testing is implemented in the Robot Framework (robotframework.org).
+
+The simplest way to execute the tests is using the helm chart in
+ric_robot_suite/helm/nanobot. See ric_robot_suite/helm/nanobot/README
+for instructions on configuring, installing, and extending the chart.
+The chart depends on the docker image in docker/nanobot.
+
+Roadmap:
+
+test/
+|- ric_robot_suite/ (this directory)
+ |- docker/
+ |- nanobot/ : Dockerfile for the robot framework image
+ |- robot-xapp/ : Dockerfile for an xapp used by some tests
+ |- helm/
+ |- nanobot/ : Chart for the robot framework
+ |- ric-python-utils : RIC test-support libraries; these are built into the
+ docker image.
+ |- robot/ : Test libraries, testsuites, and support files to be
+ built into the docker image
+ |- xapp/ : Go source and deployment files for the test xapp
\ No newline at end of file
# files/libraries, so we pick and choose what to copy..
RUN mkdir /robot/resources
COPY robot/resources/json_templater.robot /robot/resources
+COPY robot/resources/a1mediator /robot/resources/a1mediator
COPY robot/resources/appmgr /robot/resources/appmgr
COPY robot/resources/dashboard /robot/resources/dashboard
COPY robot/resources/e2mgr /robot/resources/e2mgr
# See the License for the specific language governing permissions and
# limitations under the License.
-In addition to the default robot framework container, this tree
-includes a minimal robot framework container called "nanobot" and
-intented for automated testing or resource-limited environments while
-supporting all testsuites implemented by the larger container. The
-nanobot container lacks the xvfb-based browser and lighttpd/PHP
-dashboard.
+This dockerfile implements a minimal robot framework container called
+"nanobot", intended for automated testing or resource-limited
+environments.
To build the container, first check the following ENV values at the
top of docker/nanobot/Dockerfile to ensure they reflect the RIC
Log: /log.html
Report: /report.html
-A Helm chart for the container can be found in helm/nanobot. See
-helm/nanobot/README for more information on the chart.
\ No newline at end of file
+A Helm chart for the container can be found in
+ric_robot_suite/helm/nanobot. See ric_robot_suite/helm/nanobot/README
+for more information on the chart.
\ No newline at end of file
# Copy the robot code
COPY robot /var/opt/RIC/robot
-COPY red.xml /var/opt/RIC
+COPY docker/ric-robot/red.xml /var/opt/RIC
COPY ric-python-utils /var/opt/RIC/ric-python-utils
-COPY runTags.sh /var/opt/RIC
-COPY setup.sh /var/opt/RIC
-COPY version.properties /var/opt/RIC
-COPY README.md /var/opt/RIC
+COPY docker/ric-robot/runTags.sh /var/opt/RIC
+COPY docker/ric-robot/setup.sh /var/opt/RIC
+COPY docker/ric-robot/version.properties /var/opt/RIC
+COPY docker/ric-robot/README.md /var/opt/RIC
COPY docker/ric-robot/lighttpd.conf /etc/lighttpd/lighttpd.conf
COPY docker/ric-robot/authorization /etc/lighttpd/authorization
--- /dev/null
+# ==================================================================================
+# Copyright (c) 2018-2019 AT&T Intellectual Property.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==================================================================================
+
+FROM alpine AS xapp-alpine-go-base
+
+RUN apk update && apk add build-base autoconf automake libtool cmake ninja pkgconfig git go
+
+WORKDIR /tmp
+RUN git clone https://gerrit.o-ran-sc.org/r/ric-plt/lib/rmr
+RUN mkdir /tmp/rmr/build
+WORKDIR /tmp/rmr/build
+RUN cmake .. -DPACK_EXTERNALS=1 -DDEV_PKG=1
+RUN make install
+
+RUN mkdir /tmp/robot-xapp
+COPY xapp/src /tmp/robot-xapp
+WORKDIR /tmp/robot-xapp
+ENV CGO_CFLAGS "-I /usr/local/include -L/usr/local/lib"
+RUN go build robot-xapp.go
+
+#
+FROM alpine
+
+COPY --from=xapp-alpine-go-base /usr/local/lib64/libnng.so /usr/lib/libnng.so.1
+COPY --from=xapp-alpine-go-base /tmp/robot-xapp/robot-xapp /robot-xapp
+
+COPY xapp/local.rt /local.rt
+COPY xapp/config-file.json /config-file.json
+COPY xapp/schema.json /schema.json
+
+ENV DBAAS_SERVICE_HOST service-ricplt-dbaas-tcp.ricplt.svc.cluster.local
+ENV DBAAS_SERVICE_PORT 6379
+ENV DBAAS_PORT_6379_TCP_ADDR service-ricplt-dbaas-tcp.ricplt.svc.cluster.local
+ENV DBAAS_PORT_6379_TCP_PORT 6379
+
+ENV RMR_SEED_RT /local.rt
+
+WORKDIR /
+CMD [ "/robot-xapp", "-f", "/config-file.json" ]
# Copyright (c) 2019 AT&T Intellectual Property.
-# Copyright (c) 2019 Nokia.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
This directory contains a Helm chart for the minimal robot container
and Kubernetes job.
+USING THE CHART
+To install the chart (which creates and runs a single Kubernetes job),
+first copy the packaged Common-Template chart (from
+https://gerrit.o-ran-sc.org/r/it/dep.git) into
+ric_robot_suite/helm/nanobot/charts. Then, edit
+ric_robot_suite/helm/nanobot/values.yaml to reflect your
+environment and desired tests and run
+
+$ helm install [--values <override-file> ] --namespace <namespace> nanobot
+
+... in the ric_robot_suite/helm directory, where <override-file>
+is the recipe override file used when deploying the RIC, if any. The
+output from the test will be in the job's log:
+
+kubectl logs --namespace <namespace> job/nanobot-r0-ric-robot-run
+
+(the job name may be different if you have altered the values.yaml
+file; use 'kubectl --namespace <namespace> get jobs' to find it).
+
+The namespace selected will be where the job's service account is
+created and the job runs. It may but need not be the same as the RIC
+platform or xapp namespace —- the chart may be deployed in any
+namespace.
+
+CUSTOMIZING AND EXTENDING THE CHART
The job mounts its testsuites directory as a configmap, built from
files in nanobot/configmap-src/testsuites. Template expansion is
performed on files in that directory.
robot/resources, care should be taken to ensure that names do not
collide across those directories, particularly between trees.
-
-To install the chart (which creates and runs a single Kubernetes job),
-edit values.yaml to reflect your environment and desired tests and run
-
-$ helm install [--values <override-file> ] --namespace <namespace> nanobot
-
-... where <override-file> is the recipe override file used when
-deploying the RIC, if any.
-
-The namespace selected will be where the job's service account is
-created and the job runs. It may but need not be the same as the RIC
-platform or xapp namespace —- the chart may be deployed in any
-namespace.
-
-
+The job uses the nanobot docker image built by
+ric_robot_suite/docker/nanobot/Dockerfile; see
+ric_robot_suite/docker/nanobot/README for information about the
+image.
\ No newline at end of file
{{- $xappNS := include "common.namespace.xapp" . }}
{{- $ricplt := printf "%s.svc.%s" $ricpltNS $domain }}
{{- $release := default "r1" .Values.ric.robot.release }}
+{{- $testxapp := default "robot-xapp" .Values.ric.robot.environment.xapp }}
#
*** Settings ***
Documentation store all properties that can change or are used in multiple places here
${GLOBAL_INJECTED_RTMGR_PASSWORD} {{ .Values.ric.platform.components.rtmgr.password }}
{{- end }}
#
+{{- if .Values.ric.platform.components.a1mediator }}
+${GLOBAL_A1MEDIATOR_SERVER_PROTOCOL} {{ default "http" .Values.ric.platform.components.a1mediator.protocol }}
+${GLOBAL_INJECTED_A1MEDIATOR_IP_ADDR} {{ printf "%s.%s" (include "common.servicename.a1mediator.http" .) $ricplt }}
+${GLOBAL_A1MEDIATOR_SERVER_PORT} {{ include "common.serviceport.a1mediator.http" . }}
+${GLOBAL_A1MEDIATOR_POLICY_ID} {{ default "6266268" .Values.ric.platform.components.a1mediator.policyID }}
+${GLOBAL_A1MEDIATOR_TARGET_XAPP} {{ default $testxapp .Values.ric.platform.components.a1mediator.xappName }}
+{{- end }}
+#
${GLOBAL_INJECTED_DBAAS_IP_ADDR} {{ printf "%s.%s" (include "common.servicename.dbaas.tcp" .) $ricplt }}
${GLOBAL_DBAAS_SERVER_PORT} {{ include "common.serviceport.dbaas.tcp" . }}
#
--- /dev/null
+# Copyright (c) 2019 AT&T Intellectual Property.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation Tests for the A1 Mediator
+
+Resource /robot/resources/global_properties.robot
+Resource /robot/resources/a1mediator/a1mediator_interface.robot
+Resource /robot/resources/appmgr/appmgr_interface.robot
+
+Library UUID
+
+*** Variables ***
+${POLICY_ID} ${GLOBAL_A1MEDIATOR_POLICY_ID}
+${TARGET_XAPP} ${GLOBAL_A1MEDIATOR_TARGET_XAPP}
+
+*** Test Cases ***
+Deploy Target XApp If Necessary
+ [Tags] etetests ci_tests a1tests intrusive
+ ${err} ${xappStatus} = Run Keyword And Ignore Error
+ ... Get XApp By Name ${TARGET_XAPP}
+ Run Keyword If '${err}' == 'FAIL'
+ ... Deploy XApp ${TARGET_XAPP}
+
+Create Policy
+ [Tags] etetests ci_tests a1tests intrusive
+ ${policyProperty} = Create Dictionary
+ ... type=string
+ ... description=a message
+ ${policyProperties} = Create Dictionary
+ ... message=${policyProperty}
+ ${policyName} = Generate UUID
+ ${policyName} = Convert To String ${policyName}
+ Set Suite Variable ${policyName}
+ ${resp} = Create A1 Policy Type
+ ... ${POLICY_ID}
+ ... ${policyName}
+ ... ${policyName}
+ ... ${policyProperties}
+ Should Be True ${resp}
+
+Policy Should Exist
+ [Tags] etetests ci_tests a1tests
+ ${resp} = Retrieve A1 Policy ${POLICY_ID}
+ Should Be True ${resp}
+ Should Be Equal ${resp.json()}[name] ${policyName}
+
+Create Policy Instance
+ [Tags] etetests ci_tests a1tests intrusive
+ ${instanceName} = Generate UUID
+ ${instanceName} = Convert To String ${instanceName}
+ Set Suite Variable ${instanceName}
+ ${instanceMessage} = Generate UUID
+ ${instanceMessage} = Convert To String ${instanceMessage}
+ ${instanceProperties} = Create Dictionary
+ ... message=${instanceMessage}
+ Set Suite Variable ${instanceMessage}
+ ${resp} = Instantiate A1 Policy
+ ... ${POLICY_ID}
+ ... ${instanceName}
+ ... ${instanceProperties}
+ Should Be True ${resp}
+
+Policy Should Have Instances
+ [Tags] etetests ci_tests a1tests
+ ${resp} = Retrieve A1 Instance ${POLICY_ID}
+ Should Be True ${resp}
+ Should Not Be Empty ${resp.json()}
+
+Instance Should Exist
+ [Tags] etetests ci_tests a1tests
+ ${resp} = Retrieve A1 Instance ${POLICY_ID} ${instanceName}
+ Should Be True ${resp}
+ Should Be Equal ${resp.json()}[message] ${instanceMessage}
+
+Instance Should Be IN EFFECT
+ [Tags] etetests ci_tests a1tests
+ Wait Until Keyword Succeeds 3x 5s Status Is IN EFFECT
+
+Delete Policy Instance
+ [Tags] etetests ci_tests a1tests intrusive
+ ${resp} = Delete A1 Instance ${POLICY_ID} ${instanceName}
+ Should Be True ${resp}
+
+Instance Should Not Exist
+ [Tags] etetests ci_tests a1tests
+ Wait Until Keyword Succeeds 3x 5s Instance Has Been Deleted
+
+Delete Policy
+ [Tags] etetests ci_tests a1tests intrusive
+ ${resp} = Delete A1 Policy ${POLICY_ID}
+ Should Be True ${resp}
+
+Policy Should Not Exist
+ [Tags] etetests ci_tests a1tests
+ Wait Until Keyword Succeeds 3x 5s Policy Has Been Deleted
+
+Undeploy Target XApp
+ [Tags] etetests ci_tests a1tests intrusive
+ Undeploy XApp ${TARGET_XAPP}
+
+*** Keywords ***
+Status Is IN EFFECT
+ ${resp} = Retrieve A1 Instance Status
+ ... ${POLICY_ID}
+ ... ${instanceName}
+ Should Be True ${resp}
+ Should Be Equal ${resp.json()}[instance_status] IN EFFECT
+
+Instance Has Been Deleted
+ ${resp} = Retrieve A1 Instance
+ ... ${POLICY_ID}
+ ... ${instanceName}
+ Should Be Equal As Strings ${resp.status_code} 404
+
+Policy Has Been Deleted
+ ${resp} = Retrieve A1 Policy ${POLICY_ID}
+ Should Be Equal As Strings ${resp.status_code} 404
releaseName: r0
components:
a1mediator:
+ xappName: robot-xapp
+ policyID: "6266268"
appmgr:
user: test
password: test
# This should be an xapp which is "onboarded" to
# the appmgr but not deployed at the time the test
# is initiated.
- xapp: xapp-std
+ xapp: robot-xapp
gNodeB:
# the details of a test gNodeB, for [E/X]2 tests.
# This can be either a real gnodeb or a simulator,
--- /dev/null
+*** Settings ***
+Documentation Keywords for interacting with the A1 interface, including policy creation, instantiaton, and deletion
+
+Library RequestsLibrary
+
+Resource /robot/resources/global_properties.robot
+
+*** Variables ***
+${A1MEDIATOR_BASE_PATH} /a1-p/policytypes
+${A1MEDIATOR_ENDPOINT} ${GLOBAL_A1MEDIATOR_SERVER_PROTOCOL}://${GLOBAL_INJECTED_A1MEDIATOR_IP_ADDR}:${GLOBAL_A1MEDIATOR_SERVER_PORT}
+
+*** Keywords ***
+Create A1 Policy Type
+ [Documentation] Create a new policy via the A1 Mediator.
+ [Arguments] ${type} ${name} ${description} ${properties} ${required}=@{EMPTY}
+ ${typeID} = Convert To Integer ${type}
+ Should Be True ${type} > 0 Policy type must be an integer > 0
+ ${createSchema} = Create Dictionary
+ ... $schema=http://json-schema.org/draft-07/schema#
+ ... type=object
+ ... properties=${properties}
+ ... required=@{required}
+ ${createBody} = Create Dictionary
+ ... name=${name}
+ ... policy_type_id=${typeID}
+ ... description=${description}
+ ... create_schema=${createSchema}
+ ${createJSON} = Evaluate json.dumps(&{createBody}) json,uuid
+ ${resp} = Run A1Mediator PUT Request /${type} body=${createJSON}
+ [Return] ${resp}
+
+Instantiate A1 Policy
+ [Documentation] Create a new instance of an A1 policy
+ [Arguments] ${type} ${instance} ${properties}=${EMPTY}
+ ${typeID} = Convert To Integer ${type}
+ Should Be True ${type} > 0 Policy type must be an integer > 0
+ ${instanceJSON} = Evaluate json.dumps(&{properties}) json,uuid
+ ${resp} = Run A1Mediator PUT Request /${type}/policies/${instance} body=${instanceJSON}
+ [Return] ${resp}
+
+Delete A1 Instance
+ [Documentation] Remove an A1 policy instance
+ [Arguments] ${type} ${instance}
+ ${typeID} = Convert To Integer ${type}
+ Should Be True ${type} > 0 Policy type must be an integer > 0
+ ${resp} = Run A1Mediator DELETE Request /${type}/policies/${instance}
+ [Return] ${resp}
+
+Delete A1 Policy
+ [Documentation] Remove an A1 policy type
+ [Arguments] ${type}
+ ${typeID} = Convert To Integer ${type}
+ Should Be True ${type} > 0 Policy type must be an integer > 0
+ ${resp} = Run A1Mediator DELETE Request /${type}
+ [Return] ${resp}
+
+Retrieve A1 Policy
+ [Documentation] Get a defined policy from A1
+ [Arguments] ${type}
+ ${typeID} = Convert To Integer ${type}
+ Should Be True ${type} > 0 Policy type must be an integer > 0
+ ${resp} = Run A1Mediator GET Request /${type}
+ [Return] ${resp}
+
+Retrieve A1 Instance
+ [Documentation] Get a defined policy from A1. If no instance is specified, retrieve all instances.
+ [Arguments] ${type} ${instance}=${EMPTY}
+ ${typeID} = Convert To Integer ${type}
+ Should Be True ${type} > 0 Policy type must be an integer > 0
+ ${resp} = Run Keyword If "${instance}" != "${EMPTY}"
+ ... Run A1Mediator GET Request /${type}/policies/${instance}
+ ... ELSE
+ ... Run A1Mediator GET Request /${type}/policies
+ [Return] ${resp}
+
+Retrieve A1 Instance Status
+ [Documentation] Get policy instance status
+ [Arguments] ${type} ${instance}=${EMPTY}
+ ${typeID} = Convert To Integer ${type}
+ Should Be True ${type} > 0 Policy type must be an integer > 0
+ ${resp} = Run A1Mediator GET Request /${type}/policies/${instance}/status
+ [Return] ${resp}
+
+Run A1mediator GET Request
+ [Documentation] Make an HTTP GET request against the XApp manager
+ [Arguments] ${path}=${EMPTY}
+ ${session} = Create Session roboA1mediatorGet ${A1MEDIATOR_ENDPOINT}
+ ${headers} = Create Dictionary Accept=application/json Content-Type=application/json
+ ${resp} = Get Request roboA1mediatorGet ${A1MEDIATOR_BASE_PATH}${path} headers=${headers}
+ [Return] ${resp}
+
+Run A1mediator PUT Request
+ [Documentation] Make an HTTP PUT request against the XApp manager
+ [Arguments] ${path}=${EMPTY} ${body}=${EMPTY}
+ ${session} = Create Session roboA1mediatorPut ${A1MEDIATOR_ENDPOINT}
+ ${headers} = Create Dictionary Accept=application/json Content-Type=application/json
+ ${resp} = PUT Request roboA1mediatorPut ${A1MEDIATOR_BASE_PATH}${path}
+ ... headers=${headers}
+ ... data=${body}
+ [Return] ${resp}
+
+Run A1mediator POST Request
+ [Documentation] Make an HTTP POST request against the XApp manager
+ [Arguments] ${path}=${EMPTY} ${body}=${EMPTY}
+ ${session} = Create Session roboA1mediatorPost ${A1MEDIATOR_ENDPOINT}
+ ${headers} = Create Dictionary Accept=application/json Content-Type=application/json
+ ${resp} = POST Request roboA1mediatorPost ${A1MEDIATOR_BASE_PATH}${path}
+ ... headers=${headers}
+ ... data=${body}
+ [Return] ${resp}
+
+Run A1mediator DELETE Request
+ [Documentation] Make an HTTP DELETE request against the XApp manager
+ [Arguments] ${path}
+ ${session} = Create Session roboA1mediatorDelete ${A1MEDIATOR_ENDPOINT}
+ ${headers} = Create Dictionary Accept=application/json Content-Type=application/json
+ ${resp} = Delete Request roboA1mediatorDelete ${A1MEDIATOR_BASE_PATH}${path} headers=${headers}
+ [Return] ${resp}
--- /dev/null
+This directory contains an XApp used to support RIC testing; its
+primary purpose is things like RMR message handling which aren't
+easily managed in Robot itself, and to provide a way to test XApp
+deployment and point-to-point message flow without depending on the
+functionality of any particular XApp.
+
+To build the XApp container, do
+
+$ doocker build -f docker/robot-xapp/Dockerfile .
+
+from the ric_robot_suite directory. The XApp can be deployed via the
+RIC Application Manager using the schema.json and config.json files in
+this directory.
--- /dev/null
+{
+ "local": { "host": ":8080" },
+ "logger": { "level": 5 },
+ "db": {
+ "host": "service-ricplt-dbaas-tcp.ricplt.svc.cluster.local",
+ "port": 6379,
+ "namespaces": ["robotXApp", "rnib"]
+
+ },
+ "rmr": {
+ "protPort": "tcp:4560",
+ "maxSize": 10000,
+ "numWorkers": 1,
+ "rxMessages": [ "A1_POLICY_REQ" ],
+ "txMessages": [ "A1_POLICY_RESP", "A1_POLICY_QUERY" ],
+ "policies": [ 3608368 ]
+ },
+ "metrics": [
+ { "objectName": "RobotXAppCounters",
+ "objectInstance": "PolicyCreates",
+ "name": "PolicyCreates",
+ "description": "A1 policies created",
+ "type": "counter" },
+ { "objectName": "RobotXAppCounters",
+ "objectInstance": "DuplicatePolicyCreates",
+ "name": "DuplicatePolicyCreates",
+ "description": "A1 CREATE requests received for existing policy instances",
+ "type": "counter" },
+ { "objectName": "RobotXAppCounters",
+ "objectInstance": "PolicyUpdates",
+ "name": "PolicyUpdates",
+ "description": "A1 policies updateded",
+ "type": "counter" },
+ { "objectName": "RobotXAppCounters",
+ "objectInstance": "NonexistentPolicyUpdates",
+ "name": "NonexistentPolicyUpdates",
+ "description": "A1 UPDATE requests received for nonexistent policy instances",
+ "type": "counter" },
+ { "objectName": "RobotXAppCounters",
+ "objectInstance": "PolicyDeletes",
+ "name": "PolicyDeletes",
+ "description": "A1 policies deleted",
+ "type": "counter" },
+ { "objectName": "RobotXAppCounters",
+ "objectInstance": "NonexistentPolicyDeletes",
+ "name": "NonexistentPolicyDeletes",
+ "description": "A1 DELETE requests received for nonexistent policy instances",
+ "type": "counter" },
+ { "objectName": "RobotXAppCounters",
+ "objectInstance": "dbStores",
+ "name": "dbStores",
+ "description": "SDL store requests",
+ "type": "counter" },
+ { "objectName": "RobotXAppCounters",
+ "objectInstance": "dbStoreFailures",
+ "name": "dbStoreFailures",
+ "description": "SDL store request failures",
+ "type": "counter" },
+ { "objectName": "RobotXAppCounters",
+ "objectInstance": "dbDeletes",
+ "name": "dbDeletes",
+ "description": "SDL delete requests",
+ "type": "counter" },
+ { "objectName": "RobotXAppCounters",
+ "objectInstance": "dbDeleteFailures",
+ "name": "dbDeleteFailures",
+ "description": "SDL delete request failures",
+ "type": "counter" },
+ { "objectName": "RobotXAppCounters",
+ "objectInstance": "MessagesReceived",
+ "name": "MessagesReceived",
+ "description": "Total RMR messages received",
+ "type": "counter" },
+ { "objectName": "RobotXAppCounters",
+ "objectInstance": "MessagesSent",
+ "name": "MessagesSent",
+ "description": "Total RMR messages sent",
+ "type": "counter" }
+ ]
+}
--- /dev/null
+newrt|start
+rte|20001|service-ricplt-a1mediator-rmr.ricplt:4561;service-ricplt-a1mediator-rmr.ricplt:4562
+mse|20011|3608368|service-ricplt-a1mediator-rmr.ricplt:4561;service-ricplt-a1mediator-rmr.ricplt:4562
+mse|20011|6266268|service-ricplt-a1mediator-rmr.ricplt:4561;service-ricplt-a1mediator-rmr.ricplt:4562
+newrt|end
--- /dev/null
+{
+ "definitions": {},
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "http://example.com/root.json",
+ "type": "object",
+ "title": "The Root Schema",
+ "required": [
+ "local",
+ "logger",
+ "rmr",
+ "db",
+ "controls",
+ "metrics"
+ ],
+ "properties": {
+ "local": {
+ "$id": "#/properties/local",
+ "type": "object",
+ "title": "The Local Schema",
+ "required": [
+ "host"
+ ],
+ "properties": {
+ "host": {
+ "$id": "#/properties/local/properties/host",
+ "type": "string",
+ "title": "The Host Schema",
+ "default": "",
+ "examples": [
+ ":8080"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ },
+ "logger": {
+ "$id": "#/properties/logger",
+ "type": "object",
+ "title": "The Logger Schema",
+ "required": [
+ "level"
+ ],
+ "properties": {
+ "level": {
+ "$id": "#/properties/logger/properties/level",
+ "type": "integer",
+ "title": "The Level Schema",
+ "default": 0,
+ "examples": [
+ 3
+ ]
+ }
+ }
+ },
+ "rmr": {
+ "$id": "#/properties/rmr",
+ "type": "object",
+ "title": "The Rmr Schema",
+ "required": [
+ "protPort",
+ "maxSize",
+ "numWorkers",
+ "rxMessages",
+ "txMessages"
+ ],
+ "properties": {
+ "protPort": {
+ "$id": "#/properties/rmr/properties/protPort",
+ "type": "string",
+ "title": "The Protport Schema",
+ "default": "",
+ "examples": [
+ "tcp:4560"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "maxSize": {
+ "$id": "#/properties/rmr/properties/maxSize",
+ "type": "integer",
+ "title": "The Maxsize Schema",
+ "default": 0,
+ "examples": [
+ 2072
+ ]
+ },
+ "numWorkers": {
+ "$id": "#/properties/rmr/properties/numWorkers",
+ "type": "integer",
+ "title": "The Numworkers Schema",
+ "default": 0,
+ "examples": [
+ 1
+ ]
+ },
+ "rxMessages": {
+ "$id": "#/properties/rmr/properties/rxMessages",
+ "type": "array",
+ "title": "The Rxmessages Schema",
+ "items": {
+ "$id": "#/properties/rmr/properties/rxMessages/items",
+ "type": "string",
+ "title": "The Items Schema",
+ "default": "",
+ "examples": [
+ "RIC_SUB_RESP",
+ "RIC_SUB_FAILURE",
+ "RIC_SUB_DEL_RESP",
+ "RIC_SUB_DEL_FAILURE",
+ "RIC_INDICATION"
+ ],
+ "pattern": "^(.*)$"
+ }
+ },
+ "txMessages": {
+ "$id": "#/properties/rmr/properties/txMessages",
+ "type": "array",
+ "title": "The Txmessages Schema",
+ "items": {
+ "$id": "#/properties/rmr/properties/txMessages/items",
+ "type": "string",
+ "title": "The Items Schema",
+ "default": "",
+ "examples": [
+ "RIC_SUB_REQ",
+ "RIC_SUB_DEL_REQ",
+ "RIC_SGNB_ADDITION_REQ",
+ "RIC_SGNB_ADDITION_ACK",
+ "RIC_SGNB_ADDITION_REJECT",
+ "RIC_SGNB_MOD_REQUEST",
+ "RIC_SGNB_MOD_REQUEST_ACK",
+ "RIC_SGNB_MOD_REQUEST_REJECT",
+ "RIC_SGNB_MOD_REQUIRED",
+ "RIC_SGNB_MOD_CONFIRM",
+ "RIC_SGNB_MOD_REFUSE",
+ "RIC_SGNB_RECONF_COMPLETE",
+ "RIC_SGNB_RELEASE_REQUEST",
+ "RIC_SGNB_RELEASE_CONFIRM",
+ "RIC_SGNB_RELEASE_REQUIRED",
+ "RIC_SGNB_RELEASE_REQUEST_ACK",
+ "RIC_SECONDARY_RAT_DATA_USAGE_REPORT",
+ "RIC_SN_STATUS_TRANSFER",
+ "RIC_RRC_TRANSFER",
+ "RIC_UE_CONTEXT_RELEASE"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ }
+ },
+ "db": {
+ "$id": "#/properties/db",
+ "type": "object",
+ "title": "The Db Schema",
+ "required": [
+ "host",
+ "port",
+ "namespaces"
+ ],
+ "properties": {
+ "host": {
+ "$id": "#/properties/db/properties/host",
+ "type": "string",
+ "title": "The Host Schema",
+ "default": "",
+ "examples": [
+ "localhost"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "port": {
+ "$id": "#/properties/db/properties/port",
+ "type": "integer",
+ "title": "The Port Schema",
+ "default": 0,
+ "examples": [
+ 6379
+ ]
+ },
+ "namespaces": {
+ "$id": "#/properties/db/properties/namespaces",
+ "type": "array",
+ "title": "The Namespaces Schema",
+ "items": {
+ "$id": "#/properties/db/properties/namespaces/items",
+ "type": "string",
+ "title": "The Items Schema",
+ "default": "",
+ "examples": [
+ "sdl",
+ "rnib"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ }
+ },
+ "controls": {
+ "$id": "#/properties/controls",
+ "type": "object",
+ "title": "The Controls Schema",
+ "required": [
+ "active",
+ "requestorId",
+ "ranFunctionId",
+ "ricActionId",
+ "interfaceId"
+ ],
+ "properties": {
+ "active": {
+ "$id": "#/properties/controls/properties/active",
+ "type": "boolean",
+ "title": "The Active Schema",
+ "default": false,
+ "examples": [
+ true
+ ]
+ },
+ "requestorId": {
+ "$id": "#/properties/controls/properties/requestorId",
+ "type": "integer",
+ "title": "The Requestorid Schema",
+ "default": 0,
+ "examples": [
+ 66
+ ]
+ },
+ "ranFunctionId": {
+ "$id": "#/properties/controls/properties/ranFunctionId",
+ "type": "integer",
+ "title": "The Ranfunctionid Schema",
+ "default": 0,
+ "examples": [
+ 1
+ ]
+ },
+ "ricActionId": {
+ "$id": "#/properties/controls/properties/ricActionId",
+ "type": "integer",
+ "title": "The Ricactionid Schema",
+ "default": 0,
+ "examples": [
+ 0
+ ]
+ },
+ "interfaceId": {
+ "$id": "#/properties/controls/properties/interfaceId",
+ "type": "object",
+ "title": "The Interfaceid Schema",
+ "required": [
+ "globalENBId"
+ ],
+ "properties": {
+ "globalENBId": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId",
+ "type": "object",
+ "title": "The Globalenbid Schema",
+ "required": [
+ "plmnId",
+ "eNBId"
+ ],
+ "properties": {
+ "plmnId": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/plmnId",
+ "type": "string",
+ "title": "The Plmnid Schema",
+ "default": "",
+ "examples": [
+ "310150"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "eNBId": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/eNBId",
+ "type": "integer",
+ "title": "The Enbid Schema",
+ "default": 0,
+ "examples": [
+ 202251
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "metrics": {
+ "$id": "#/properties/metrics",
+ "type": "array",
+ "title": "The Metrics Schema",
+ "items": {
+ "$id": "#/properties/metrics/items",
+ "type": "object",
+ "title": "The Items Schema",
+ "required": [
+ "objectName",
+ "objectInstance",
+ "name",
+ "type",
+ "description"
+ ],
+ "properties": {
+ "objectName": {
+ "$id": "#/properties/metrics/items/properties/objectName",
+ "type": "string",
+ "title": "The Objectname Schema",
+ "default": "",
+ "examples": [
+ "UEEventStreamingCounters"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "objectInstance": {
+ "$id": "#/properties/metrics/items/properties/objectInstance",
+ "type": "string",
+ "title": "The Objectinstance Schema",
+ "default": "",
+ "examples": [
+ "SgNBAdditionRequest"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "name": {
+ "$id": "#/properties/metrics/items/properties/name",
+ "type": "string",
+ "title": "The Name Schema",
+ "default": "",
+ "examples": [
+ "SgNBAdditionRequest"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "type": {
+ "$id": "#/properties/metrics/items/properties/type",
+ "type": "string",
+ "title": "The Type Schema",
+ "default": "",
+ "examples": [
+ "counter"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "description": {
+ "$id": "#/properties/metrics/items/properties/description",
+ "type": "string",
+ "title": "The Description Schema",
+ "default": "",
+ "examples": [
+ "The total number of SG addition request events processed"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+go 1.12
+
+module gerrit.o-ran-sc.org/r/it/test/ric_robot_suite/xapp
+
+require (
+ gerrit.o-ran-sc.org/r/ric-plt/xapp-frame v0.0.20
+)
+
+replace gerrit.o-ran-sc.org/r/ric-plt/xapp-frame => gerrit.o-ran-sc.org/r/ric-plt/xapp-frame.git v0.0.21
+replace gerrit.o-ran-sc.org/r/ric-plt/sdlgo => gerrit.o-ran-sc.org/r/ric-plt/sdlgo.git v0.5.0
+replace gerrit.o-ran-sc.org/r/com/golog => gerrit.o-ran-sc.org/r/com/golog.git v0.0.1
--- /dev/null
+/*
+==================================================================================
+ Copyright (c) 2019 AT&T Intellectual Property.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================================
+*/
+package main
+
+import "gerrit.o-ran-sc.org/r/ric-plt/xapp-frame/pkg/xapp"
+import "encoding/json"
+import "strconv"
+
+
+type robotPolicy struct {
+ Message string `json:"message"`
+}
+
+type A1Policy struct {
+ Operation string `json:"operation"`
+ Type int `json:"policy_type_id"`
+ Instance string `json:"policy_instance_id"`
+ Payload robotPolicy `json:"payload"`
+}
+
+type A1Response struct {
+ Type int `json:"policy_type_id"`
+ Instance string `json:"policy_instance_id"`
+ Handler string `json:"handler_id"`
+ Status string `json:"status"`
+}
+
+type robotXApp struct {
+ metrics map[string]xapp.Counter
+}
+
+func sdlKey(t int, i string) (key string) {
+ return strconv.Itoa(t) +"|"+ i
+}
+
+func (r robotXApp) SendA1Response (t int, i string, handler string, status string) {
+ /* A1_POLICY_RESP, _ := xapp.Rmr.GetRicMessageId("A1_POLICY_RESP") */
+ A1_POLICY_RESP := 20011
+ msg, _ := json.Marshal(A1Response {
+ Type: t,
+ Instance: i,
+ Handler: handler,
+ Status: status,
+ })
+
+ xapp.Logger.Debug("Outgoing A1 Response %s (length %d)", string(msg), len(msg))
+ /* fixme: check response for errors */
+ xapp.Rmr.SendMsg(&xapp.RMRParams {
+ Mtype: A1_POLICY_RESP,
+ Payload: msg,
+ PayloadLen: len(msg),
+ Xid: "",
+ SubId: t,
+ })
+ r.metrics["MessagesSent"].Inc()
+}
+
+func (r robotXApp) CreatePolicy(t int, i string, policy robotPolicy) {
+ k := sdlKey(t, i)
+ err := xapp.Sdl.Store(k, policy.Message)
+ if err == nil {
+ xapp.Logger.Debug("Created instance %s of policy %d", i, t)
+ r.metrics["PolicyCreates"].Inc()
+ r.metrics["dbStores"].Inc()
+ r.SendA1Response(t, i, "robot", "OK")
+ } else {
+ xapp.Logger.Error("Failed to create DB record for instance %s of policy %d: %v", i, t, err)
+ r.metrics["dbStoreFailures"].Inc()
+ r.SendA1Response(t, i, "robot", "ERROR")
+ }
+}
+
+func (r robotXApp) DeletePolicy(t int, i string) {
+ k := sdlKey(t, i)
+
+ policies, _ := xapp.Sdl.Read(k)
+ existingPolicy, _ := policies[k]
+
+ if existingPolicy != nil {
+ err := xapp.Sdl.Delete([]string{k})
+ if err == nil {
+ xapp.Logger.Debug("Deleted instance %s of policy %d, old value: %s", i, t, existingPolicy)
+ r.metrics["PolicyDeletes"].Inc()
+ r.metrics["dbDeletes"].Inc()
+ r.SendA1Response(t, i, "robot", "DELETED")
+ } else {
+ xapp.Logger.Error("Failed to delete DB record for instance %s of policy %d: %v", i, t, err)
+ r.metrics["dbDeleteFailures"].Inc()
+ r.SendA1Response(t, i, "robot", "ERROR")
+ }
+ } else {
+ xapp.Logger.Error("Attempt to delete nonexistent instance %s of policy %d", i, t)
+ r.metrics["NonexistentPolicyDeletes"].Inc()
+ r.SendA1Response(t, i, "robot", "ERROR")
+ }
+}
+
+func (r robotXApp) Consume(msg *xapp.RMRParams) (err error) {
+ /* this is returning 0. will investigate and fix someday. */
+ /* A1_POLICY_REQ, _ := xapp.Rmr.GetRicMessageId("A1_POLICY_REQ") */
+ A1_POLICY_REQ := 20010
+
+ xapp.Logger.Debug("Message received - type=%d, Src=%s (%s), payload=%s",
+ msg.Mtype, xapp.Rmr.GetRicMessageName(msg.Mtype), msg.Src, string(msg.Payload))
+
+ /* this is bogus right now, but we'll eventually support more than one message
+ also, xapps really should handle messages in a separate goroutine, but there's
+ no real need in this one as we're not latency bound */
+ if msg.Mtype == A1_POLICY_REQ {
+ var a1Msg A1Policy
+ err := json.Unmarshal(msg.Payload, &a1Msg)
+
+ xapp.Logger.Debug("... Policy request - err=%v|op=%s|type=%d|instance=%s",
+ err, a1Msg.Operation, a1Msg.Type, a1Msg.Instance)
+ switch a1Msg.Operation {
+ case "CREATE":
+ go r.CreatePolicy(a1Msg.Type, a1Msg.Instance, a1Msg.Payload)
+ case "DELETE":
+ go r.DeletePolicy(a1Msg.Type, a1Msg.Instance)
+ }
+ }
+ return nil
+}
+
+func main() {
+ counters := []xapp.CounterOpts {
+ { Name: "PolicyCreates", Help: "A1 policies created" },
+ { Name: "DuplicatePolicyCreates",
+ Help: "A1 CREATE requests received for existing policy instances" },
+ { Name: "PolicyUpdates", Help: "A1 policies updateded" },
+ { Name: "NonexistentPolicyUpdates",
+ Help: "A1 UPDATE requests received for nonexistent policy instances" },
+ { Name: "PolicyDeletes", Help: "A1 policies deleted" },
+ { Name: "NonexistentPolicyDeletes",
+ Help: "A1 DELETE requests received for nonexistent policy instances" },
+ { Name: "dbStores",
+ Help: "SDL store requests" },
+ { Name: "dbStoreFailures",
+ Help: "SDL store request failures" },
+ { Name: "dbDeletes",
+ Help: "SDL delete requests" },
+ { Name: "dbDeleteFailures",
+ Help: "SDL delete request failures" },
+ { Name: "MessagesReceived",
+ Help: "Total RMR messages received" },
+ { Name: "MessagesSent",
+ Help: "Total RMR messages sent" },
+ }
+ xapp.Run(robotXApp{ metrics: xapp.Metric.RegisterCounterGroup(counters, "robotXApp")})
+}