VOL-2499 Robot testing framework for alarms

Change-Id: If8e66344f5bb84c242a3e16fe6b162c44f7d02f2
diff --git a/Makefile b/Makefile
index 3b6f8c0..0719cdb 100644
--- a/Makefile
+++ b/Makefile
@@ -94,6 +94,11 @@
 failure-test: ROBOT_CONFIG_FILE := $(ROBOT_FAIL_SINGLE_PON_FILE)
 failure-test: voltha-test
 
+bbsim-alarms-kind: ROBOT_MISC_ARGS += -X -i active
+bbsim-alarms-kind: ROBOT_FILE := Voltha_AlarmTests.robot
+bbsim-alarms-kind: ROBOT_CONFIG_FILE := $(ROBOT_SCALE_SINGLE_PON_FILE)
+bbsim-alarms-kind: voltctl-docker-image-build voltctl-docker-image-install-kind voltha-test
+
 voltha-test: ROBOT_MISC_ARGS += -e notready
 
 voltha-test: vst_venv
@@ -174,3 +179,10 @@
 
 clean-all: clean
 	rm -rf vst_venv gendocs
+
+voltctl-docker-image-build:
+	cd docker && docker build -t opencord/voltctl:local -f Dockerfile.voltctl .
+
+voltctl-docker-image-install-kind:
+	@if [ "`kind get clusters | grep voltha`" = '' ]; then echo "no voltha cluster found" && exit 1; fi
+	kind load docker-image --name `kind get clusters | grep voltha` opencord/voltctl:local
diff --git a/docker/Dockerfile.voltctl b/docker/Dockerfile.voltctl
new file mode 100644
index 0000000..d17711f
--- /dev/null
+++ b/docker/Dockerfile.voltctl
@@ -0,0 +1,21 @@
+# Copyright 2018-present Open Networking Foundation
+#
+# 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 busybox:1.31.1-glibc
+
+RUN mkdir -p /usr/bin
+RUN wget -O - https://github.com/opencord/voltctl/releases/download/v1.0.6/voltctl-1.0.6-linux-amd64 > /usr/bin/voltctl
+COPY volt.config /root/.volt/config
+RUN chmod a+x /usr/bin/voltctl && sync && voltctl completion bash >> /root/.bashrc
+CMD ["sh", "-c", "sleep infinity"]
\ No newline at end of file
diff --git a/docker/volt.config b/docker/volt.config
new file mode 100644
index 0000000..991e01e
--- /dev/null
+++ b/docker/volt.config
@@ -0,0 +1,28 @@
+
+# Copyright 2019-present Ciena Corporation
+#
+# 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.
+#
+
+apiVersion: v2
+server: voltha-api.voltha:55555
+kafka: voltha-kafka-0.voltha-kafka-headless.voltha:9092
+tls:
+  useTls: false
+  caCert: ""
+  cert: ""
+  key: ""
+  verify: ""
+grpc:
+  timeout: 10s
+
diff --git a/libraries/k8s.robot b/libraries/k8s.robot
index c3b67fe..24a1ca6 100644
--- a/libraries/k8s.robot
+++ b/libraries/k8s.robot
@@ -51,6 +51,41 @@
     ...    kubectl delete pod ${restart_pod_name} -n ${namespace} --grace-period=0 --force
     Log    ${output}
 
+Exec Pod
+    [Arguments]    ${namespace}    ${name}    ${command}
+    [Documentation]    Uses kubectl to execute a command in the pod and return the output
+    ${rc}    ${exec_pod_name}=    Run and Return Rc and Output
+    ...    kubectl get pods -n ${namespace} | grep ${name} | awk 'NR==1{print $1}'
+    Log    ${exec_pod_name}
+    Should Not Be Empty    ${exec_pod_name}    Unable to parse pod name
+    ${rc}    ${output}=    Run and Return Rc and Output
+    ...    kubectl exec -i ${exec_pod_name} -n ${namespace} -- ${command}
+    Log    ${output}
+    [return]    ${output}
+
+Exec Pod Separate Stderr
+    [Arguments]    ${namespace}    ${name}    ${command}
+    [Documentation]    Uses kubectl to execute a command in the pod and return the stderr and stdout
+    ${rc}    ${exec_pod_name}=    Run and Return Rc and Output
+    ...    kubectl get pods -n ${namespace} | grep ${name} | awk 'NR==1{print $1}'
+    Log    ${exec_pod_name}
+    Should Not Be Empty    ${exec_pod_name}    Unable to parse pod name
+    @{args}=     Split String    ${command}
+    ${result}=    Run Process
+    ...    kubectl     exec     -i     ${exec_pod_name}     -n     ${namespace}     --     @{args}
+    ${stdout}=    Set Variable    ${result.stdout}
+    ${stderr}=    Set Variable    ${result.stderr}
+    Log    ${stdout}
+    Log    ${stderr}
+    [return]    ${stdout}    ${stderr}
+
+Apply Kubernetes Resources
+    [Arguments]    ${resource_yaml}    ${namespace}
+    [Documentation]    Use kubectl to create resources given a yaml file
+    ${rc}    Run and Return Rc
+    ...    kubectl apply -n ${namespace} -f ${resource_yaml}
+    Should Be Equal as Integers    ${rc}    0
+
 Validate Pod Status
     [Arguments]    ${pod_name}    ${namespace}   ${expectedStatus}
     [Documentation]    To run the kubectl command and check the status of the given pod matches the expected status
diff --git a/libraries/utils.robot b/libraries/utils.robot
index fed2694..779d987 100644
--- a/libraries/utils.robot
+++ b/libraries/utils.robot
@@ -320,3 +320,20 @@
         ...    ${dst['dp_iface_name']}.${src['s_tag']}    ${dst['ip']}    ${dst['user']}    ${dst['pass']}
         ...    ${dst['container_type']}    ${dst['container_name']}
     END
+
+Should Be Larger Than
+    [Documentation]    Verify that value_1 is > value_2
+    [Arguments]    ${value_1}    ${value_2}
+    Run Keyword If    ${value_1} <= ${value_2}
+    ...    Fail    The value ${value_1} is not larger than ${value_2}
+
+Should Be Float
+    [Documentation]    Verify that value is a floating point number type
+    [Arguments]    ${value}
+    ${type}    Evaluate    type(${value}).__name__
+    Should Be Equal    ${type}    float
+
+Get Current Time
+    [Documentation]    Return the current time in RFC3339 format
+    ${output}=    Run    date -u +"%FT%T%:z"
+    [return]     ${output}
diff --git a/libraries/voltctl.robot b/libraries/voltctl.robot
index 87ec701..6c942db 100644
--- a/libraries/voltctl.robot
+++ b/libraries/voltctl.robot
@@ -384,6 +384,15 @@
     Log    ${sn}
     [Return]    ${sn}
 
+Get Parent ID From Device ID
+    [Arguments]    ${device_id}
+    [Documentation]    Gets the device id by matching for ${device_id}
+    ${rc}    ${pid}=    Run and Return Rc and Output
+    ...    ${VOLTCTL_CONFIG}; voltctl device list --filter=Id=${device_id} --format='{{.ParentId}}'
+    Should Be Equal As Integers    ${rc}    0
+    Log    ${pid}
+    [Return]    ${pid}
+
 Validate Device Removed
     [Arguments]    ${id}
     [Documentation]    Verifys that device, ${serial_number}, has been removed
diff --git a/tests/functional/Voltha_AlarmTests.robot b/tests/functional/Voltha_AlarmTests.robot
new file mode 100755
index 0000000..06a71b6
--- /dev/null
+++ b/tests/functional/Voltha_AlarmTests.robot
@@ -0,0 +1,162 @@
+#Copyright 2017-present Open Networking Foundation
+#
+# 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     This test raises alarms using bbsimctl and verifies them using voltctl
+Suite Setup       Setup Suite
+Suite Teardown    Teardown Suite
+Library           Collections
+Library           String
+Library           OperatingSystem
+Library           XML
+Library           RequestsLibrary
+Library           ../../libraries/DependencyLibrary.py
+Resource          ../../libraries/onos.robot
+Resource          ../../libraries/voltctl.robot
+Resource          ../../libraries/utils.robot
+Resource          ../../libraries/k8s.robot
+Resource          ../../variables/variables.robot
+
+*** Variables ***
+${timeout}        60s
+${long_timeout}    420s
+${of_id}          0
+${logical_id}     0
+${has_dataplane}    True
+${external_libs}    True
+${setup_device}    True
+${teardown_device}    True
+${VOLTCTL_NAMESPACE}      default
+${BBSIMCTL_NAMESPACE}      voltha
+${VOLTCTL_POD_NAME}    voltctl
+${BBSIMCTL_POD_NAME}    bbsim
+
+*** Test Cases ***
+Ensure required pods Running
+    [Documentation]    Ensure the bbsim and voltctl pods are in Running state
+    [Tags]    active
+    Validate Pod Status    ${BBSIMCTL_POD_NAME}    ${BBSIMCTL_NAMESPACE}    Running
+    Validate Pod Status    ${VOLTCTL_POD_NAME}    ${VOLTCTL_NAMESPACE}     Running
+
+ONU Discovery
+    [Documentation]    Discover lists of ONUS, their Serial Numbers and device id, and pick one for subsequent tests
+    [Tags]    active
+    #build onu sn list
+    ${List_ONU_Serial}    Create List
+    Set Suite Variable    ${List_ONU_Serial}
+    Build ONU SN List    ${List_ONU_Serial}
+    Log    ${List_ONU_Serial}
+    #validate onu states
+    Wait Until Keyword Succeeds    ${long_timeout}    20s
+    ...    Validate ONU Devices    ENABLED    ACTIVE    REACHABLE    ${List_ONU_Serial}
+    # Pick an ONU to use for subsequent test cases
+    ${onu_sn}    Set Variable    ${List_ONU_Serial}[0]
+    Set Suite Variable    ${onu_sn}
+    ${onu_id}    Get Device ID From SN    ${onu_sn}
+    Set Suite Variable    ${onu_id}
+
+Test StartupFailureAlarm
+    [Documentation]    Raise StartupFailure Alarm and verify event received
+    [Tags]    not_active
+    Raise Alarm    StartupFailure    ${onu_sn}
+    # This one is actually broken...
+    # TODO: complete test once alarm is working...
+
+Test RaiseLossOfBurstAlarm
+    [Documentation]    Raise Loss Of Burst Alarm and verify event received
+    [Tags]    active
+    ${since}    Get Current Time
+    Raise Alarm    LossOfBurst    ${onu_sn}
+    ${header}    ${deviceEvent}    Get Device Event    ONU_LOSS_OF_BURST_RAISE_EVENT    ${since}
+    Verify Header   ${header}    Voltha.openolt.ONU_LOSS_OF_BURST\.(\\d+)
+    Should Be Equal    ${deviceEvent}[deviceEventName]    ONU_LOSS_OF_BURST_RAISE_EVENT
+    # TODO: Why does the event have the OLT ID instead of the ONU ID ? Verify correctness.
+    ${parent_id}    Get Parent ID From Device ID     ${onu_id}
+    Should Be Equal    ${deviceEvent}[resourceId]    ${parent_id}
+
+Test ClearLossOfBurstAlarm
+    [Documentation]    Clear Loss Of Burst Alarm and verify event received
+    [Tags]    active
+    ${since}    Get Current Time
+    Clear Alarm    LossOfBurst    ${onu_sn}
+    ${header}    ${deviceEvent}    Get Device Event    ONU_LOSS_OF_BURST_CLEAR_EVENT    ${since}
+    Verify Header   ${header}    Voltha.openolt.ONU_LOSS_OF_BURST\.(\\d+)
+    Should Be Equal    ${deviceEvent}[deviceEventName]    ONU_LOSS_OF_BURST_CLEAR_EVENT
+    # TODO: Why does the event have the OLT ID instead of the ONU ID ? Verify correctness.
+    ${parent_id}    Get Parent ID From Device ID     ${onu_id}
+    Should Be Equal    ${deviceEvent}[resourceId]    ${parent_id}
+
+*** Keywords ***
+Setup Suite
+    [Documentation]    Set up the test suite
+    Common Test Suite Setup
+    # Ensure the voltctl pod is deployed and running
+    Apply Kubernetes Resources    ./voltctl.yaml    ${VOLTCTL_NAMESPACE}
+    Wait Until Keyword Succeeds    ${timeout}    5s
+    ...    Validate Pod Status    ${VOLTCTL_POD_NAME}    ${VOLTCTL_NAMESPACE}     Running
+    # Call Setup keyword in utils library to create and enable device
+    Run Keyword If    ${setup_device}    Setup
+
+Teardown Suite
+    [Documentation]    Clean up devices if desired
+    ...    kills processes and cleans up interfaces on src+dst servers
+    Run Keyword If    ${external_libs}    Get ONOS Status    ${k8s_node_ip}
+    Run Keyword If    ${has_dataplane}    Clean Up Linux
+    Run Keyword If    ${external_libs}
+    ...    Log Kubernetes Containers Logs Since Time    ${datetime}    ${container_list}
+    Run Keyword If    ${teardown_device}    Delete Device and Verify
+    Run Keyword If    ${teardown_device}    Test Empty Device List
+    Run Keyword If    ${teardown_device}    Execute ONOS CLI Command    ${k8s_node_ip}    ${ONOS_SSH_PORT}
+    ...    device-remove ${of_id}
+
+Raise Alarm
+    [Documentation]    Raise an Alarm
+    [Arguments]    ${name}    ${sn}
+    ${raiseOutput}    Exec Pod    ${BBSIMCTL_NAMESPACE}     ${BBSIMCTL_POD_NAME}    bbsimctl alarm raise ${name} ${sn}
+    Should Contain    ${raiseOutput}    Alarm Indication Sent
+
+Clear Alarm
+    [Documentation]    Raise an Alarm
+    [Arguments]    ${name}    ${sn}
+    ${raiseOutput}    Exec Pod    ${BBSIMCTL_NAMESPACE}     ${BBSIMCTL_POD_NAME}    bbsimctl alarm clear ${name} ${sn}
+    Should Contain    ${raiseOutput}    Alarm Indication Sent
+
+Get Device Event
+    [Documentation]    Get the most recent alarm event from voltha.events
+    [Arguments]    ${deviceEventName}    ${since}
+    ${output}    ${raiseErr}    Exec Pod Separate Stderr   ${VOLTCTL_NAMESPACE}     ${VOLTCTL_POD_NAME}
+    ...    voltctl event listen --show-body -t 1 -o json -f Titles=${deviceEventName} -s ${since}
+    ${json}    To Json    ${output}
+    ${count}    Get Length    ${json}
+    # If there is more than one event (which could happen if we quickly do a raise and a clear),
+    # then return the most recent one.
+    Should Be Larger Than   ${count}    0
+    ${lastIndex}    Evaluate    ${count}-1
+    ${lastItem}    Set Variable    ${json}[${lastIndex}]
+    ${header}    Set Variable    ${lastItem}[header]
+    ${deviceEvent}    Set Variable   ${lastItem}[deviceEvent]
+    Log    ${header}
+    Log    ${deviceEvent}
+    [return]    ${header}    ${deviceEvent}
+
+Verify Header
+    [Documentation]    Verify that a DeviceEvent's header is sane and the id matches regex
+    [Arguments]    ${header}    ${id}
+    Should Be Equal   ${header}[subCategory]    ONU
+    Should Be Equal   ${header}[type]    DEVICE_EVENT
+    Should Match Regexp    ${header}[id]    ${id}
+    # TODO Revisit when timestamp format is changed from Float to Timestamp
+    Should Be Float   ${header}[raisedTs]
+    Should Be Float   ${header}[reportedTs]
+
diff --git a/tests/functional/voltctl.yaml b/tests/functional/voltctl.yaml
new file mode 100644
index 0000000..0f0554d
--- /dev/null
+++ b/tests/functional/voltctl.yaml
@@ -0,0 +1,37 @@
+# Copyright 2018-present Open Networking Foundation
+#
+# 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.
+
+---
+
+apiVersion: apps/v1beta2
+kind: Deployment
+metadata:
+  name: voltctl
+  labels:
+    app: voltctl
+    chart: voltctl
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: voltctl
+  template:
+    metadata:
+      labels:
+        app: voltctl
+    spec:
+      containers:
+        - name: voltctl
+          image: opencord/voltctl:local
+          imagePullPolicy: IfNotPresent