IMPL: A1 <-> XApp Message Flow Testing 51/2551/5
authordave kormann <dk3239@att.com>
Sat, 15 Feb 2020 08:38:42 +0000 (03:38 -0500)
committerdave kormann <dk3239@att.com>
Thu, 27 Feb 2020 23:36:11 +0000 (18:36 -0500)
This change adds support for testing RMR message flow between the A1 mediator
and XApps.  It consists of two major components:

o An XApp which receives policy updates from A1
o A Robot Framework testsuite to send policy requests to A1

This change also cleans up the ric_robot_suite subdirectory somewhat as part
of an ongoing process of improving robot usability.

Signed-off-by: dave kormann <dk3239@att.com>
Change-Id: I589e9202ab277924ac848f912a01ee718cb17e14

35 files changed:
README.md
ric_robot_suite/README [new file with mode: 0644]
ric_robot_suite/docker/nanobot/Dockerfile
ric_robot_suite/docker/nanobot/README [moved from ric_robot_suite/README-nanobot with 85% similarity]
ric_robot_suite/docker/ric-robot/Dockerfile
ric_robot_suite/docker/ric-robot/README.md [moved from ric_robot_suite/README.md with 100% similarity]
ric_robot_suite/docker/ric-robot/build.sh [moved from ric_robot_suite/build.sh with 100% similarity]
ric_robot_suite/docker/ric-robot/docgen.py [moved from ric_robot_suite/docgen.py with 100% similarity]
ric_robot_suite/docker/ric-robot/eteshare/config/integration_robot_properties.py [moved from ric_robot_suite/eteshare/config/integration_robot_properties.py with 100% similarity]
ric_robot_suite/docker/ric-robot/eteshare/config/vm_properties.py [moved from ric_robot_suite/eteshare/config/vm_properties.py with 100% similarity]
ric_robot_suite/docker/ric-robot/eteshare/health/index.php [moved from ric_robot_suite/eteshare/health/index.php with 100% similarity]
ric_robot_suite/docker/ric-robot/eteshare/nodeb/setup/index.php [moved from ric_robot_suite/eteshare/nodeb/setup/index.php with 100% similarity]
ric_robot_suite/docker/ric-robot/eteshare/ric/v1/xapps/DemoXapp1/101/index.php [moved from ric_robot_suite/eteshare/ric/v1/xapps/DemoXapp1/101/index.php with 100% similarity]
ric_robot_suite/docker/ric-robot/eteshare/ric/v1/xapps/DemoXapp1/index.php [moved from ric_robot_suite/eteshare/ric/v1/xapps/DemoXapp1/index.php with 100% similarity]
ric_robot_suite/docker/ric-robot/eteshare/ric/v1/xapps/index.php [moved from ric_robot_suite/eteshare/ric/v1/xapps/index.php with 100% similarity]
ric_robot_suite/docker/ric-robot/html/index.html [moved from ric_robot_suite/html/index.html with 100% similarity]
ric_robot_suite/docker/ric-robot/red.xml [moved from ric_robot_suite/red.xml with 100% similarity]
ric_robot_suite/docker/ric-robot/runTags.sh [moved from ric_robot_suite/runTags.sh with 100% similarity]
ric_robot_suite/docker/ric-robot/setup.sh [moved from ric_robot_suite/setup.sh with 100% similarity]
ric_robot_suite/docker/ric-robot/version.properties [moved from ric_robot_suite/version.properties with 100% similarity]
ric_robot_suite/docker/robot-xapp/Dockerfile [new file with mode: 0644]
ric_robot_suite/helm/nanobot/README
ric_robot_suite/helm/nanobot/charts/.PLACEHOLDER [new file with mode: 0644]
ric_robot_suite/helm/nanobot/configmap-src/public/properties/global_properties.robot
ric_robot_suite/helm/nanobot/configmap-src/public/testsuites/a1mediator.robot [new file with mode: 0644]
ric_robot_suite/helm/nanobot/values.yaml
ric_robot_suite/helm/ric-robot/README.md [moved from ric_robot_suite/helm/README.md with 100% similarity]
ric_robot_suite/helm/ric-robot/robot_install.sh [moved from ric_robot_suite/helm/robot_install.sh with 100% similarity]
ric_robot_suite/robot/resources/a1mediator/a1mediator_interface.robot [new file with mode: 0644]
ric_robot_suite/xapp/README [new file with mode: 0644]
ric_robot_suite/xapp/config-file.json [new file with mode: 0644]
ric_robot_suite/xapp/local.rt [new file with mode: 0644]
ric_robot_suite/xapp/schema.json [new file with mode: 0644]
ric_robot_suite/xapp/src/go.mod [new file with mode: 0644]
ric_robot_suite/xapp/src/robot-xapp.go [new file with mode: 0755]

index bb51058..3fad272 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,8 +1,12 @@
-# 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.
diff --git a/ric_robot_suite/README b/ric_robot_suite/README
new file mode 100644 (file)
index 0000000..832cbe8
--- /dev/null
@@ -0,0 +1,23 @@
+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
index 062dbe4..dcdfed2 100644 (file)
@@ -47,6 +47,7 @@ COPY ric-python-utils/ricutils/*.py /robot/lib/python/
 # 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
similarity index 85%
rename from ric_robot_suite/README-nanobot
rename to ric_robot_suite/docker/nanobot/README
index 526d2b8..1c8811e 100644 (file)
 #   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
@@ -73,5 +70,6 @@ Output:  /output.xml
 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
index e44a47f..ea1b13e 100644 (file)
@@ -67,12 +67,12 @@ RUN pip install robotframework==3.0.4 \
 # 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
 
diff --git a/ric_robot_suite/docker/robot-xapp/Dockerfile b/ric_robot_suite/docker/robot-xapp/Dockerfile
new file mode 100644 (file)
index 0000000..03607a1
--- /dev/null
@@ -0,0 +1,52 @@
+# ==================================================================================
+#       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" ]
index 5813187..ce2b1e4 100644 (file)
@@ -1,5 +1,4 @@
 #   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.
@@ -46,18 +70,7 @@ Because both resources/ and properties/ are mounted at
 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
diff --git a/ric_robot_suite/helm/nanobot/charts/.PLACEHOLDER b/ric_robot_suite/helm/nanobot/charts/.PLACEHOLDER
new file mode 100644 (file)
index 0000000..e69de29
index 3c15f62..83e96bf 100644 (file)
@@ -17,6 +17,7 @@
 {{- $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
@@ -61,6 +62,14 @@ ${GLOBAL_INJECTED_RTMGR_USER}         {{ .Values.ric.platform.components.rtmgr.u
 ${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" .  }}
 #
diff --git a/ric_robot_suite/helm/nanobot/configmap-src/public/testsuites/a1mediator.robot b/ric_robot_suite/helm/nanobot/configmap-src/public/testsuites/a1mediator.robot
new file mode 100644 (file)
index 0000000..69c2eeb
--- /dev/null
@@ -0,0 +1,129 @@
+#   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
index 353f069..78f622e 100644 (file)
@@ -55,6 +55,8 @@ ric:
   releaseName: r0
   components:
    a1mediator:
+    xappName: robot-xapp
+    policyID: "6266268"
    appmgr:
     user: test
     password: test
@@ -131,7 +133,7 @@ ric:
     # 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,
diff --git a/ric_robot_suite/robot/resources/a1mediator/a1mediator_interface.robot b/ric_robot_suite/robot/resources/a1mediator/a1mediator_interface.robot
new file mode 100644 (file)
index 0000000..059596f
--- /dev/null
@@ -0,0 +1,118 @@
+*** 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}
diff --git a/ric_robot_suite/xapp/README b/ric_robot_suite/xapp/README
new file mode 100644 (file)
index 0000000..6130572
--- /dev/null
@@ -0,0 +1,13 @@
+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.
diff --git a/ric_robot_suite/xapp/config-file.json b/ric_robot_suite/xapp/config-file.json
new file mode 100644 (file)
index 0000000..26aae59
--- /dev/null
@@ -0,0 +1,80 @@
+{
+ "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" }
+ ]
+}
diff --git a/ric_robot_suite/xapp/local.rt b/ric_robot_suite/xapp/local.rt
new file mode 100644 (file)
index 0000000..8833ba6
--- /dev/null
@@ -0,0 +1,5 @@
+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
diff --git a/ric_robot_suite/xapp/schema.json b/ric_robot_suite/xapp/schema.json
new file mode 100644 (file)
index 0000000..ebfad88
--- /dev/null
@@ -0,0 +1,357 @@
+{
+  "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
diff --git a/ric_robot_suite/xapp/src/go.mod b/ric_robot_suite/xapp/src/go.mod
new file mode 100644 (file)
index 0000000..cb23942
--- /dev/null
@@ -0,0 +1,11 @@
+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
diff --git a/ric_robot_suite/xapp/src/robot-xapp.go b/ric_robot_suite/xapp/src/robot-xapp.go
new file mode 100755 (executable)
index 0000000..b2ae40a
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+==================================================================================
+  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")})
+}