From cc685917b28444e6e9fc70f1a96b14e073d8cec7 Mon Sep 17 00:00:00 2001 From: dave kormann Date: Sat, 21 Mar 2020 20:44:36 -0400 Subject: [PATCH] IMPL: RICPLT-3081 Automate O1 med flow This change adds support for testing the O1 Mediator via netconf; this includes adding netconf-ssh libraries to the robot. Issue-ID: RICPLT-3081 signed-off-by: dave kormann Change-Id: I33082cc5e668e01516d8163c2f0da8e385b309cc --- ric_robot_suite/docker/nanobot/Dockerfile | 46 ++++++----- ric_robot_suite/docker/nanobot/container-tag.yaml | 2 +- ric_robot_suite/docker/ric-robot/Dockerfile | 14 ++-- .../public/properties/global_properties.robot | 10 +++ .../public/resources/mcxapp_interface.robot | 64 +++++++++++++++ .../public/testsuites/o1mediator.robot | 92 ++++++++++++++++++++++ ric_robot_suite/helm/nanobot/values.yaml | 10 ++- .../ric-python-utils/ricutils/E2SimUtils.py | 19 +++++ .../ric-python-utils/ricutils/KubernetesEntity.py | 78 +++++++++++++----- .../o1mediator/o1mediator_interface.robot | 89 +++++++++++++++++++++ 10 files changed, 375 insertions(+), 49 deletions(-) create mode 100644 ric_robot_suite/helm/nanobot/configmap-src/public/resources/mcxapp_interface.robot create mode 100644 ric_robot_suite/helm/nanobot/configmap-src/public/testsuites/o1mediator.robot create mode 100644 ric_robot_suite/robot/resources/o1mediator/o1mediator_interface.robot diff --git a/ric_robot_suite/docker/nanobot/Dockerfile b/ric_robot_suite/docker/nanobot/Dockerfile index dcdfed2..f87df67 100644 --- a/ric_robot_suite/docker/nanobot/Dockerfile +++ b/ric_robot_suite/docker/nanobot/Dockerfile @@ -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. @@ -12,10 +11,25 @@ # 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 python:3-alpine -MAINTAINER "RIC" +FROM python:3-alpine AS nanobot-build + +RUN apk update && apk add git build-base libffi-dev libxml2 libxslt libxml2-dev libxslt-dev openssl-dev + +RUN pip install UUID +RUN pip install kubernetes +RUN pip install redis +RUN pip install asyncio +RUN pip install websockets +RUN pip install robotframework +RUN pip install robotframework-requests +RUN pip install robotframework-ncclient + +WORKDIR /tmp/ +RUN git clone -b 3.0.1-ONAP https://gerrit.onap.org/r/testsuite/python-testing-utils.git +FROM python:3-alpine +MAINTAINER "RIC" LABEL name="Docker image for the RIC Robot Testing Framework" ENV ROBOT_HOME="/robot" @@ -26,20 +40,16 @@ ENV RICPLT_NAMESPACE=ricplt ENV RICPLT_RELEASE_NAME=ric-full ENV RICPLT_COMPONENTS="a1mediator appmgr dbaas e2mgr e2term rtmgr" +RUN apk update && apk add libxslt +COPY --from=nanobot-build /usr/local/lib/python3.8 /usr/local/lib/python3.8 +COPY --from=nanobot-build /usr/local/bin/robot /usr/local/bin + RUN mkdir -p /robot/lib/python # ONAP eteutils -# we only need a few things from this so we won't install -# the whole thing. -# we could do this in a builder image but that seems execssive. -RUN apk update && apk add git -WORKDIR /tmp/ -RUN git clone -b 3.0.1-ONAP https://gerrit.onap.org/r/testsuite/python-testing-utils.git -RUN cp /tmp/python-testing-utils/eteutils/StringTemplater.py /robot/lib/python -RUN cp /tmp/python-testing-utils/eteutils/UUID.py /robot/lib/python -RUN rm -rf /tmp/python-testing-utils -RUN apk del --purge git - +# we only need a few things from this so we won't install the whole thing. +COPY --from=nanobot-build /tmp/python-testing-utils/eteutils/StringTemplater.py /robot/lib/python +COPY --from=nanobot-build /tmp/python-testing-utils/eteutils/UUID.py /robot/lib/python COPY ric-python-utils/ricutils/*.py /robot/lib/python/ # @@ -47,12 +57,12 @@ 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 COPY robot/resources/e2sim /robot/resources/e2sim COPY robot/resources/e2term /robot/resources/e2term +COPY robot/resources/o1mediator /robot/resources/o1mediator COPY robot/resources/ric /robot/resources/ric COPY robot/resources/rnib /robot/resources/rnib COPY robot/resources/rtmgr /robot/resources/rtmgr @@ -64,12 +74,6 @@ COPY robot/resources/global_properties.robot /robot/resources RUN mkdir -p /robot/assets/templates COPY robot/assets/templates/e2mgr_setup_nodeb.template /robot/assets/templates -RUN pip install UUID -RUN pip install kubernetes -RUN pip install redis -RUN pip install robotframework -RUN pip install robotframework-requests - RUN python -m compileall /robot/lib/python WORKDIR / diff --git a/ric_robot_suite/docker/nanobot/container-tag.yaml b/ric_robot_suite/docker/nanobot/container-tag.yaml index 079c0dc..12585e7 100644 --- a/ric_robot_suite/docker/nanobot/container-tag.yaml +++ b/ric_robot_suite/docker/nanobot/container-tag.yaml @@ -1,2 +1,2 @@ --- -tag: 0.0.1 +tag: 0.0.2 diff --git a/ric_robot_suite/docker/ric-robot/Dockerfile b/ric_robot_suite/docker/ric-robot/Dockerfile index ea1b13e..aa8e0a4 100644 --- a/ric_robot_suite/docker/ric-robot/Dockerfile +++ b/ric_robot_suite/docker/ric-robot/Dockerfile @@ -31,12 +31,16 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone ENV BUILDTIME=true # Install Python, Pip, Robot framework, chromium, lighttpd web server + +# temporarily removed chromium-browser and chromium-chromedriver as +# they've moved to snaps and we don't have snapd support in this container +# yet. This will break robot tests that depend on the browser, but we don't +# have those in the RIC at this point anyway. + RUN apt-get update \ && apt-get install \ --no-install-recommends \ --assume-yes \ - chromium-browser \ - chromium-chromedriver \ dnsutils \ git \ gcc \ @@ -47,11 +51,9 @@ RUN apt-get update \ net-tools \ php \ php-cgi \ - python2.7 \ + python3-pip \ python-dev \ python-setuptools \ - python-wheel \ - python-pip \ python-redis \ unzip \ vim \ @@ -61,7 +63,7 @@ RUN apt-get update \ xxd -RUN pip install robotframework==3.0.4 \ +RUN pip3 install robotframework==3.0.4 \ && python --version # Copy the robot code diff --git a/ric_robot_suite/helm/nanobot/configmap-src/public/properties/global_properties.robot b/ric_robot_suite/helm/nanobot/configmap-src/public/properties/global_properties.robot index 83e96bf..704112f 100644 --- a/ric_robot_suite/helm/nanobot/configmap-src/public/properties/global_properties.robot +++ b/ric_robot_suite/helm/nanobot/configmap-src/public/properties/global_properties.robot @@ -70,6 +70,16 @@ ${GLOBAL_A1MEDIATOR_POLICY_ID} {{ default "6266268" .Values.ric.plat ${GLOBAL_A1MEDIATOR_TARGET_XAPP} {{ default $testxapp .Values.ric.platform.components.a1mediator.xappName }} {{- end }} # +{{- if .Values.ric.platform.components.o1mediator }} +${GLOBAL_O1MEDIATOR_HOST} {{ printf "%s.%s" (include "common.servicename.o1mediator.tcp.netconf" .) $ricplt }} +${GLOBAL_O1MEDIATOR_PORT} {{ include "common.serviceport.o1mediator.tcp.netconf" . }} +${GLOBAL_O1MEDIATOR_USER} {{ .Values.ric.platform.components.o1mediator.user }} +${GLOBAL_O1MEDIATOR_PASSWORD} {{ .Values.ric.platform.components.o1mediator.password }} +${GLOBAL_O1MEDIATOR_TARGET_XAPP} {{ default $testxapp .Values.ric.platform.components.o1mediator.xapp.name }} +${GLOBAL_O1MEDIATOR_XAPP_VERSION} {{ default "1.0" .Values.ric.platform.components.o1mediator.xapp.version }} +${GLOBAL_O1MEDIATOR_DEPLOYMENT_WAIT} {{ default "180" .Values.ric.platform.components.o1mediator.xapp.wait }} +{{- 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/resources/mcxapp_interface.robot b/ric_robot_suite/helm/nanobot/configmap-src/public/resources/mcxapp_interface.robot new file mode 100644 index 0000000..3194aad --- /dev/null +++ b/ric_robot_suite/helm/nanobot/configmap-src/public/resources/mcxapp_interface.robot @@ -0,0 +1,64 @@ +# 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 Tools for interacting with the MC XApp + +Resource /robot/resources/global_properties.robot +Resource /robot/resources/mcxapp_properties.robot + +Library Collections +Library String +Library KubernetesEntity ${GLOBAL_XAPP_NAMESPACE} + +*** Variables *** + +${listenerStatSubstring} (mcl) mtype= +${listenerStatRegex} ^\\s*([0-9]+)\\s+\\[STAT\\]\\s+\\(mcl\\)\\s+mtype=([^\\s]+)\\s+total\\s+writes=([0-9]+)\\s+total\\s+drops=([0-9]+);.*writes=([0-9]+)\\s+drops=([0-9]+) + +*** Keywords *** +Retrieve Listener Message Counts + ${pods} = Retrieve Pods For Deployment ${MCDeployment} + # for now, i'm just going to completely ignore the possibility + # of multiple MC XApp pods. that seems safe, i think. + ${pod} = Get From List ${pods} 0 + ${log} = Retrieve Log For Pod ${pod} tail=500 + ${statLogs} = Get Matches ${log} glob=*${listenerStatSubstring}* + ${stats} = Parse Listener Statistics ${statLogs} + [Return] ${stats} + +Parse Listener Statistics + [Arguments] ${logLines} + # while it's almost certainly safe to assume the log + # lines are sorted by timestamp, it's not something i'm + # going to take for granted. + ${stats} = Create Dictionary + :FOR ${statLine} IN @{logLines} + \ ${match} ${ts} ${mtype} ${tWrites} ${tDrops} ${rWrites} ${rDrops} = + ... Should Match Regexp ${statLine} ${listenerStatRegex} + \ ${stat} = Create Dictionary + ... timestamp=${ts} + ... totalWrites=${tWrites} + ... totalDrops=${tDrops} + ... recentWrites=${rWrites} + ... recentDrops=${rDrops} + \ ${s} ${d} = Run Keyword And Ignore Error + ... Get From Dictionary ${stats} ${mtype} + \ ${prevTS} = Run Keyword If "${s}" == "PASS" + ... Get From Dictionary ${d} timestamp + ... ELSE + ... Set Variable -1 + \ Run Keyword If ${ts} > ${prevTS} + ... Set To Dictionary ${stats} ${mtype} ${stat} + [Return] ${stats} diff --git a/ric_robot_suite/helm/nanobot/configmap-src/public/testsuites/o1mediator.robot b/ric_robot_suite/helm/nanobot/configmap-src/public/testsuites/o1mediator.robot new file mode 100644 index 0000000..35a5b09 --- /dev/null +++ b/ric_robot_suite/helm/nanobot/configmap-src/public/testsuites/o1mediator.robot @@ -0,0 +1,92 @@ +# 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 UE Event Collector XApp + +Resource /robot/resources/global_properties.robot + +Resource /robot/resources/o1mediator/o1mediator_interface.robot +Resource /robot/resources/ric/ric_utils.robot + +Library String +Library Collections +Library XML + +Library KubernetesEntity ${GLOBAL_RICPLT_NAMESPACE} + +*** Variables *** +${sessionPfx} = nanobot-O1 + +*** Test Cases *** +O1 Mediator Should Be Available + [Tags] etetests o1mediatortests + ${o1} = Get From Dictionary ${GLOBAL_RICPLT_COMPONENTS} o1mediator + ${deploy} = Deployment ${o1} + ${status} = Most Recent Availability Condition @{deploy.status.conditions} + Should Be Equal As Strings ${status} True ignore_case=True + +Connect To O1 Mediator + [tags] etetests o1mediatortests + ${sessionGensym} = Generate Random String + ${O1Session} = Set Variable ${sessionPfx}-${sessionGensym} + Set Suite Variable ${O1Session} + Set Suite Variable ${O1Session} + ${status} = Establish O1 Session + ... ${GLOBAL_O1MEDIATOR_USER} + ... ${GLOBAL_O1MEDIATOR_PASSWORD} + ... ${O1Session} + ... ${GLOBAL_O1MEDIATOR_HOST} + ... ${GLOBAL_O1MEDIATOR_PORT} + Should Be True ${status} + +Get O1 State + [tags] etetests o1mediatortests + ${conf} = Retrieve O1 State ${O1Session} + # just going to let this bail at a lower layer if + # the Get fails. Might be better to look for ric stuff + # in the active modules, though. + ${confXML} = Element To String ${conf} + +Deploy XApp Via O1 + [tags] etetests o1mediatortests intrusive + Deploy An XApp Using O1 ${O1Session} + ... ${GLOBAL_O1MEDIATOR_TARGET_XAPP} + ... ${GLOBAL_O1MEDIATOR_XAPP_VERSION} + +XApp Should Be Running + [tags] etetests o1mediatortests + Wait For Deployment ${GLOBAL_XAPP_NAMESPACE}-${GLOBAL_O1MEDIATOR_TARGET_XAPP} + ... timeout=${GLOBAL_O1MEDIATOR_DEPLOYMENT_WAIT} + ... namespace=${GLOBAL_XAPP_NAMESPACE} + +Undeploy XApp Via O1 + [tags] etetests o1mediatortests intrusive + Remove An XApp Using O1 ${O1Session} + ... ${GLOBAL_O1MEDIATOR_TARGET_XAPP} + ... ${GLOBAL_O1MEDIATOR_XAPP_VERSION} + +XApp Should Not Be Running + [tags] etetests o1mediatortests + ${status} ${deploy} = Run Keyword And Ignore Error + ... Deployment ${GLOBAL_XAPP_NAMESPACE}-${GLOBAL_O1MEDIATOR_TARGET_XAPP} + ${status} = Run Keyword If '${status}' == 'PASS' + ... Most Recent Availablity Condition @{deploy.status.conditions} + ... ELSE + ... Set Variable False + Should Be Equal As Strings '${status}' 'False' + +Disconnect From O1 + [tags] etetests o1mediatortests + Close O1 Session ${O1Session} diff --git a/ric_robot_suite/helm/nanobot/values.yaml b/ric_robot_suite/helm/nanobot/values.yaml index 78f622e..c2a224c 100644 --- a/ric_robot_suite/helm/nanobot/values.yaml +++ b/ric_robot_suite/helm/nanobot/values.yaml @@ -29,7 +29,7 @@ images: run: repository: nexus3.o-ran-sc.org:10004 name: o-ran-sc/it-test-nanobot - tag: 0.0.1 + tag: 0.0.2 # note: the helm chart does not create repository credential secrets. # If your repository requires authentication, create a docker-registry # secret with @@ -69,6 +69,12 @@ ric: user: test password: test submgr: + o1mediator: + user: test + password: test + xapp: + name: robot-xapp + version: 1.0 xapp: mcxapp: listener: @@ -92,7 +98,7 @@ ric: # # If specified, a host filesystem # path where robot output will be stored - # log: /opt/ric/robot/log + log: /opt/ric/robot/log # # Active testsuites can be chosen by # tag, testuite names, or both. leaving diff --git a/ric_robot_suite/ric-python-utils/ricutils/E2SimUtils.py b/ric_robot_suite/ric-python-utils/ricutils/E2SimUtils.py index d5f3fa1..409ad65 100644 --- a/ric_robot_suite/ric-python-utils/ricutils/E2SimUtils.py +++ b/ric_robot_suite/ric-python-utils/ricutils/E2SimUtils.py @@ -56,3 +56,22 @@ class E2SimUtils(object): def randomRANName(self, prefix=""): prefix = prefix + ''.join(random.choice(string.ascii_uppercase) for _ in range(4-(min(4, len(prefix))))) return prefix[0:4].upper() + ''.join(random.choice(string.digits) for _ in range(6)) + + def TranslategNodeBID(self, prefix, plmn, bits): + # given a gNodeB type prefix, a plmn (as a string, no spaces), and a bitstring + # return a gNodeB ID + plmn = ''.join(filter(lambda c: c in list(string.hexdigits), plmn)) + bits = ''.join(filter(lambda c: c in ['0', '1'], bits)) + mnc3 = (int(plmn[2:4],16) & 0xf0) >> 4 + if mnc3 == 15: + return '%s:%d%d%d-0%d%d-%x' % \ + (prefix,\ + int(plmn[0:2],16) & 0xf, (int(plmn[0:2],16) & 0xf0) >> 4, int(plmn[2:4],16) & 0xf,\ + int(plmn[4:6],16) & 0x0f, (int(plmn[4:6],16) & 0xf0) >> 4, \ + int((bits + "0" * (len(bits)%4)),2)) + else: + return '%s:%d%d%d-%d%d%d-%x' % \ + (prefix,\ + int(plmn[0:2],16) & 0xf, (int(plmn[0:2],16) & 0xf0) >> 4, int(plmn[2:4],16) & 0xf,\ + int(plmn[4:6],16) & 0x0f, (int(plmn[4:6],16) & 0xf0) >> 4, mnc3,\ + int((bits + "0" * (len(bits)%4)),2)) diff --git a/ric_robot_suite/ric-python-utils/ricutils/KubernetesEntity.py b/ric_robot_suite/ric-python-utils/ricutils/KubernetesEntity.py index 8944167..f4583bc 100644 --- a/ric_robot_suite/ric-python-utils/ricutils/KubernetesEntity.py +++ b/ric_robot_suite/ric-python-utils/ricutils/KubernetesEntity.py @@ -14,10 +14,14 @@ # limitations under the License. from kubernetes import client, config +import sys import string import random import time -import sys +import ssl +import asyncio +import websockets +import urllib.parse # This library provides a massively-simplified interface to the kubernetes # API library to reduce bloat in robot tests. @@ -34,30 +38,30 @@ class KubernetesEntity(object): self._k8sCore = client.CoreV1Api() self._k8sEV1B1 = client.ExtensionsV1beta1Api() - def Deployment(self, name): + def Deployment(self, name, namespace=None): # this will throw kubernetes.client.rest.ApiException if # the deployment doesn't exist. we'll let robot cope with # that. # calling code will most likely want to check that # deploy.status.replicas == deploy.status.available_replicas - return self._k8sApp.read_namespaced_deployment(namespace=self._ns, + return self._k8sApp.read_namespaced_deployment(namespace=namespace or self._ns, name=name) - def Service(self, name): + def Service(self, name, namespace=None): # as above, we'll rely on this to throw if the svc dne. # not much to check directly here. calling code will want # to hit svc.spec.cluster_ip:r.spec.ports[0..n] with some # sort of health-check request - return self._k8sCore.read_namespaced_service(namespace=self._ns, + return self._k8sCore.read_namespaced_service(namespace=namespace or self._ns, name=name) - def Pod(self, name): - return self._k8sCore.read_namespaced_pod(namespace=self._ns, + def Pod(self, name, namepsace=None): + return self._k8sCore.read_namespaced_pod(namespace=namespace or self._ns, name=name) - def Redeploy(self, name, wait=True, timeout=30): + def Redeploy(self, name, wait=True, timeout=30, namespace=None): # restart an existing deployment by doing a nonsense update # to its spec. body = {'spec': @@ -66,36 +70,72 @@ class KubernetesEntity(object): {'annotations': { self._annotationGensym: str(time.time()) }}}}} - r = self._k8sEV1B1.patch_namespaced_deployment(namespace=self._ns, + r = self._k8sEV1B1.patch_namespaced_deployment(namespace=namespace or self._ns, name=name, body=body) if wait: - r = self.WaitForDeployment(name, timeout) + r = self.WaitForDeployment(name, timeout, namespace=namespace or self._ns) return r - def WaitForDeployment(self, name, timeout=30): + def WaitForDeployment(self, name, timeout=30, namespace=None): # block until a deployment is available while timeout > 0: - dep = self.Deployment(name) + dep = self.Deployment(name, namespace=namespace or self._ns) if dep and dep.status.conditions[-1].type == 'Available': return True time.sleep(1) timeout -= 1 raise TimeoutError('Kubernetes timeout waiting for ' + name + ' to become available') - def RetrievePodsForDeployment(self, name): + def RetrievePodsForDeployment(self, name, namespace=None): # return the pod names associated with a deployment - d = self.Deployment(name) + d = self.Deployment(name, namespace or self._ns) labels = d.spec.selector.match_labels - pods = self._k8sCore.list_namespaced_pod(self._ns, + pods = self._k8sCore.list_namespaced_pod(namespace or self._ns, label_selector=",".join(map(lambda k: k + "=" + labels[k], labels))) - return map(lambda i: i.metadata.name, pods.items) - - def RetrieveLogForPod(self, pod, container='', tail=sys.maxsize): + return list(map(lambda i: i.metadata.name, pods.items)) + + def RetrieveLogForPod(self, pod, container='', tail=sys.maxsize, namespace=None): # not really an "entity" thing per se, but. # kinda want to include timestamps, but i don't have a use case for them. - return self._k8sCore.read_namespaced_pod_log(namespace=self._ns, + return self._k8sCore.read_namespaced_pod_log(namespace=namespace or self._ns, name=pod, container=container, tail_lines=tail).split('\n')[0:-1] + + def ExecuteCommandInPod(self, pod, cmd, strip_newlines=True, namespace=None): + # near as i can tell, the python k8s client doesn't implement + # 'kubectl exec'. this is near enough for our purposes. + # 'cmd' is an argv list. + channels={1: 'stdout', 2: 'stderr', 3: 'k8s'} + output={'stdout': [], 'stderr': [], 'k8s': []} + path='/api/v1/namespaces/%s/pods/%s/exec?%s&stdin=false&stderr=true&stdout=true&tty=false' % \ + (namespace or self._ns, pod, urllib.parse.urlencode({'command': cmd}, doseq=True)) + # we could probably cache and reuse the sslcontext, but meh, we're not + # after performance here. + ctx=ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + c = client.Configuration() + + async def ExecCoroutine(): + # base64.channel.k8s.io is also a valid subprotocol, but i don't see any + # reason to support it. + async with websockets.connect(uri,\ + ssl=ctx,\ + subprotocols=["channel.k8s.io"],\ + extra_headers=c.api_key) as ws: + async for message in ws: + if message[0] in channels and (not strip_newlines or len(message) > 1): + # we probably should throw up if we get an unrecognized channel, but + # i really don't want to be bothered with asyncio exception handling + # for that vanishingly improbable case. + output[channels[message[0]]].extend(message[1:-1].decode('utf-8').split('\n')) + + ctx.load_verify_locations(c.ssl_ca_cert) + if(c.cert_file and c.key_file): + ctx.load_cert_chain(c.cert_file, c.key_file) + uri = 'wss://%s%s' % (c.host.lstrip('https://'), path) + + asyncio.get_event_loop().run_until_complete(ExecCoroutine()) + + return(output) diff --git a/ric_robot_suite/robot/resources/o1mediator/o1mediator_interface.robot b/ric_robot_suite/robot/resources/o1mediator/o1mediator_interface.robot new file mode 100644 index 0000000..f869d95 --- /dev/null +++ b/ric_robot_suite/robot/resources/o1mediator/o1mediator_interface.robot @@ -0,0 +1,89 @@ +# 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 *** +Library XML use_lxml=True +Library NcclientLibrary + +*** Variables *** +${XAppNS} urn:o-ran:ric:xapp-desc:1.0 +${NetconfNS} urn:ietf:params:xml:ns:netconf:base:1.0 +${ricXML} = +${configXML} ${ricXML} + + +*** Keywords *** +Establish O1 Session + [Arguments] ${user} + ... ${password} + ... ${session} + ... ${host}=service-ricplt-o1mediator-tcp-netconf.ricplt + ... ${port}=830 + ... ${hostkey_verify}=${False} + ... ${key}=/dev/null + ${status} = Connect host=${host} + ... port=${port} + ... username=${user} + ... password=${password} + ... key_filename=${key} + ... look_for_keys=False + ... alias=${session} + [Return] ${status} + +Retrieve O1 State + [Arguments] ${session} + # this doesn't actually seem to result in filtered XML, + # but it matches what the O1 CLI does. + ${filter} = Parse XML ${ricXML} + ${config} = Get ${session} filter_criteria=${filter} + [Return] ${config} + +Retrieve O1 Running Configuration + [Arguments] ${session} + ${config} = Get Config ${session} running + [Return] ${config} + +Deploy An XApp Using O1 + [Arguments] ${session} ${app} ${version} + ${xappCreateXML} = Generate XApp Deployment XML ${app} ${version} create + Edit Config ${session} running ${xappCreateXML} + +Remove An XApp Using O1 + [Arguments] ${session} ${app} ${version} + ${xappDeleteXML} = Generate XApp Deployment XML ${app} ${version} delete + Edit Config ${session} running ${xappDeleteXML} + +Close O1 Session + [Arguments] ${session} + Close Session ${session} + +*** Keywords *** +Generate XApp Deployment XML + [Arguments] ${name} ${version} ${operation} + ${XML} = Parse XML ${configXML} + Add Element ${XML} + ... + ... xpath=ric + Add Element ${XML} + ... xpath=ric/xapps + Add Element ${XML} + ... ${name} + ... xpath=ric/xapps/xapp + Add Element ${XML} xapp-${name} + ... xpath=ric/xapps/xapp + Add Element ${XML} ${version} + ... xpath=ric/xapps/xapp + Add Element ${XML} ${GLOBAL_XAPP_NAMESPACE} + ... xpath=ric/xapps/xapp + [Return] ${XML} \ No newline at end of file -- 2.16.6