Testplan, suite, variables and support library for basic PCEP testing. 42/17242/5
authorVratko Polak <vrpolak@cisco.com>
Fri, 27 Mar 2015 14:48:25 +0000 (15:48 +0100)
committerJamo Luhrsen <james.luhrsen@hp.com>
Wed, 1 Apr 2015 22:50:42 +0000 (22:50 +0000)
https://wiki.opendaylight.org/view/BGP_LS_PCEP:Lithium_Feature_Tests#PCEP
specifies odl-bgpcep-pcep-all as an user-facing feature.
By M4, an automated test (not necessarily complete)
should be running in CI.

This is the fourth version of suite for testing basic PCEP,
this version is finally ready for merge.

Changes (added and missing) against previous patch sets:
+ Use space-only separation, formatted by RIDE
- Still using .robot extension
+ Documentation and docstrings added everywhere
+ Setup steps compacted to one Suite Setup keyword
+ Suite Teardown added, various log gathering happens there
+ Non-critical tests now can fail on Read Until
+ Start_Pcc_Mock now does tee to a file, its contents goes to Log
+ HfsJson library added, for sorting and indenting JSON strings
+ ${MININET} substituted to place where PCC IP address should be
+ Base64 code for tunnel name is computed in the variable file
+ Long JSON data is in the variable file, in readable form
+ PEP 8 compliant
+ pylint output addressed (not all, pylint is wrong sometimes)
+ Python files now do contain license headers
- actually two sections, with slight incompatibilities between them
+ Robot file now contains a single license section, in suite documentation
- No tests for TCPMD5, they will be added as another Change

Example Sandbox run for this patch set:
https://jenkins.opendaylight.org/sandbox/job/bgpcep-csit-1node-imds-basicpcep-only-master/17/

After this change is merged, separate change will introduce
csit job to releng/builder, triggered by upstream distribution jobs.

After that is merged, another change will add this suite
to the list of suites to be run in integration-verify job.
Until then, integration-verify job is insensitive to this suite.

Change-Id: I24a8704c07780a2a97904284eb3f14de1dde50b3
Signed-off-by: Vratko Polak <vrpolak@cisco.com>
test/csit/libraries/HsfJson/hsf_json.py [new file with mode: 0644]
test/csit/libraries/HsfJson/hsfl.py [new file with mode: 0644]
test/csit/libraries/HsfJson/hsfod.py [new file with mode: 0644]
test/csit/suites/bgpcep/basicpcep/basicpcep.robot [new file with mode: 0644]
test/csit/testplans/bgpcep-basicpcep.txt [new file with mode: 0644]
test/csit/variables/basicpcep/variables.py [new file with mode: 0644]

diff --git a/test/csit/libraries/HsfJson/hsf_json.py b/test/csit/libraries/HsfJson/hsf_json.py
new file mode 100644 (file)
index 0000000..b3d5d93
--- /dev/null
@@ -0,0 +1,48 @@
+"""This module contains single a function for normalizing JSON strings."""
+# Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+
+__author__ = "Vratko Polak"
+__copyright__ = "Copyright(c) 2015, Cisco Systems, Inc."
+__license__ = "Eclipse Public License v1.0"
+__email__ = "vrpolak@cisco.com"
+
+try:
+    import simplejson as _json
+except ImportError:  # Python2.7 calls it json.
+    import json as _json
+from hsfl import Hsfl as _Hsfl
+from hsfod import Hsfod as _Hsfod
+
+
+def _hsfl_array(s_and_end, scan_once, **kwargs):
+    """Scan JSON array as usual, but return hsfl instead of list."""
+    values, end = _json.decoder.JSONArray(s_and_end, scan_once, **kwargs)
+    return _Hsfl(values), end
+
+
+class _Decoder(_json.JSONDecoder):
+    """Private class to act as customized JSON decoder.
+
+    Based on: http://stackoverflow.com/questions/10885238/
+    python-change-list-type-for-json-decoding"""
+    def __init__(self, **kwargs):
+        """Initialize decoder with special array implementation."""
+        _json.JSONDecoder.__init__(self, **kwargs)
+        # Use the custom JSONArray
+        self.parse_array = _hsfl_array
+        # Use the python implemenation of the scanner
+        self.scan_once = _json.scanner.py_make_scanner(self)
+
+
+def hsf_json(text):  # pylint likes lowercase, Robot shall understand Hsf_Json
+    """Return sorted indented JSON string, or an error message string."""
+    try:
+        object_decoded = _json.loads(text, cls=_Decoder, object_hook=_Hsfod)
+    except ValueError as err:
+        return str(err) + '\n' + text
+    pretty_json = _json.dumps(object_decoded, separators=(',', ': '), indent=1)
+    return pretty_json + '\n'  # to avoid diff "no newline" warning line
diff --git a/test/csit/libraries/HsfJson/hsfl.py b/test/csit/libraries/HsfJson/hsfl.py
new file mode 100644 (file)
index 0000000..99e70a4
--- /dev/null
@@ -0,0 +1,36 @@
+"""This module contains single class, to store a sorted list."""
+# Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+
+__author__ = "Vratko Polak"
+__copyright__ = "Copyright(c) 2015, Cisco Systems, Inc."
+__license__ = "Eclipse Public License v1.0"
+__email__ = "vrpolak@cisco.com"
+
+
+class Hsfl(list):
+    """
+    Hashable sorted frozen list implementation stub.
+
+    Supports only __init__, __repr__ and __hash__ methods.
+    Other list methods are available, but they may break contract.
+    """
+
+    def __init__(self, *args, **kwargs):
+        """Contruct super, sort and compute repr and hash cache values."""
+        sup = super(Hsfl, self)
+        sup.__init__(*args, **kwargs)
+        sup.sort(key=repr)
+        self.__repr = repr(tuple(self))
+        self.__hash = hash(self.__repr)
+
+    def __repr__(self):
+        """Return cached repr string."""
+        return self.__repr
+
+    def __hash__(self):
+        """Return cached hash."""
+        return self.__hash
diff --git a/test/csit/libraries/HsfJson/hsfod.py b/test/csit/libraries/HsfJson/hsfod.py
new file mode 100644 (file)
index 0000000..8b19a9d
--- /dev/null
@@ -0,0 +1,40 @@
+"""This module contains single class, to store a sorted dict."""
+# Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+
+__author__ = "Vratko Polak"
+__copyright__ = "Copyright(c) 2015, Cisco Systems, Inc."
+__license__ = "Eclipse Public License v1.0"
+__email__ = "vrpolak@cisco.com"
+
+import collections as _collections
+
+
+class Hsfod(_collections.OrderedDict):
+    """
+    Hashable sorted (by key) frozen OrderedDict implementation stub.
+
+    Supports only __init__, __repr__ and __hash__ methods.
+    Other OrderedDict methods are available, but they may break contract.
+    """
+
+    def __init__(self, *args, **kwargs):
+        """Put arguments to OrderedDict, sort, pass to super, cache values."""
+        self_unsorted = _collections.OrderedDict(*args, **kwargs)
+        items_sorted = sorted(self_unsorted.items(), key=repr)
+        sup = super(Hsfod, self)  # possibly something else than OrderedDict
+        sup.__init__(items_sorted)
+        # Repr string is used for sorting, keys are more important than values.
+        self.__repr = '{' + repr(self.keys()) + ':' + repr(self.values()) + '}'
+        self.__hash = hash(self.__repr)
+
+    def __repr__(self):
+        """Return cached repr string."""
+        return self.__repr
+
+    def __hash__(self):
+        """Return cached hash."""
+        return self.__hash
diff --git a/test/csit/suites/bgpcep/basicpcep/basicpcep.robot b/test/csit/suites/bgpcep/basicpcep/basicpcep.robot
new file mode 100644 (file)
index 0000000..0c9fa13
--- /dev/null
@@ -0,0 +1,105 @@
+*** Settings ***
+Documentation     Basic tests for odl-bgpcep-pcep-all feature.
+...
+...               Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+...
+...               This program and the accompanying materials are made available under the
+...               terms of the Eclipse Public License v1.0 which accompanies this distribution,
+...               and is available at http://www.eclipse.org/legal/epl-v10.html
+Suite Setup       Set_It_Up
+Suite Teardown    Tear_It_Down
+Library           OperatingSystem
+Library           SSHLibrary    prompt=]>
+Library           ${CURDIR}/../../../libraries/RequestsLibrary.py
+Library           ${CURDIR}/../../../libraries/HsfJson/hsf_json.py
+Variables         ${CURDIR}/../../../variables/Variables.py
+Variables         ${CURDIR}/../../../variables/basicpcep/variables.py    ${MININET}
+
+*** Variables ***
+${ExpDir}         ${CURDIR}/expected
+${ActDir}         ${CURDIR}/actual
+
+*** Test Cases ***
+Topology_Precondition
+    [Documentation]    Compare current pcep-topology to "offjson" variable.
+    ...    Timeout is long enough to see that pcep is ready, with no PCC is connected.
+    [Tags]    critical
+    Wait_Until_Keyword_Succeeds    900    1    Compare_Topology    ${offjson}    Pre
+
+Start_Pcc_Mock
+    [Documentation]    Execute pcc-mock on Mininet, fail is Open is not sent, keep it running for next tests.
+    ${command}=    Set_Variable    java -jar ${filename} --local-address ${MININET} --remote-address ${CONTROLLER} 2>&1 | tee pccmock.log
+    Log    ${command}
+    Write    ${command}
+    Read_Until    started, sent proposal Open
+
+Topology_Intercondition
+    [Documentation]    Compare pcep-topology to "onjson", which includes a tunnel from pcc-mock.
+    [Tags]    critical
+    Wait_Until_Keyword_succeeds    30    1    Compare_Topology    ${onjson}    Inter
+
+Stop_Pcc_Mock
+    [Documentation]    Send ctrl+c to pcc-mock, fails if no prompt is seen
+    ...    after 3 seconds (the default for SSHLibrary)
+    ${command}=    Evaluate    chr(int(3))
+    Log    ${command}
+    Write    ${command}
+    Read_Until_Prompt
+
+Topology_Postcondition
+    [Documentation]    Compare curent pcep-topology to "offjson" again.
+    ...    Timeout is lower than in Precondition,
+    ...    but data from pcc-mock should be gone quickly.
+    [Tags]    critical
+    Wait_Until_Keyword_Succeeds    30    1    Compare_Topology    ${offjson}    Post
+
+*** Keywords ***
+Set_It_Up
+    [Documentation]    Create SSH session to Mininet machine, prepare HTTP client session to Controller.
+    ...    Figure out latest pcc-mock version and download it from Nexus to Mininet.
+    ...    Also, delete and create directories for json diff handling.
+    Open_Connection    ${MININET}
+    Login_With_Public_Key    ${MININET_USER}    ${USER_HOME}/.ssh/id_rsa    any
+    Create_Session    ses    http://${CONTROLLER}:8181/restconf/operational/network-topology:network-topology    auth=${AUTH}
+    ${urlbase}=    Set_Variable    https://nexus.opendaylight.org/content/repositories/opendaylight.snapshot/org/opendaylight/bgpcep/pcep-pcc-mock
+    ${version}=    Execute_Command    curl ${urlbase}/maven-metadata.xml | grep latest | cut -d '>' -f 2 | cut -d '<' -f 1
+    Log    ${version}
+    ${namepart}=    Execute_Command    curl ${urlbase}/${version}/maven-metadata.xml | grep value | head -n 1 | cut -d '>' -f 2 | cut -d '<' -f 1
+    Log    ${namepart}
+    Set_Suite_Variable    ${filename}    pcep-pcc-mock-${namepart}-executable.jar
+    Log    ${filename}
+    ${response}=    Execute_Command    wget -q -N ${urlbase}/${version}/${filename} 2>&1
+    Log    ${response}
+    Remove_Directory    ${ExpDir}
+    Remove_Directory    ${ActDir}
+    Create_Directory    ${ExpDir}
+    Create_Directory    ${ActDir}
+
+Compare_Topology
+    [Arguments]    ${expected}    ${name}
+    [Documentation]    Get current pcep-topology as json, normalize both expected and actual json.
+    ...    Save normalized jsons to files for later processing.
+    ...    Error codes and normalized jsons should match exactly.
+    ${normexp}=    Hsf_Json    ${expected}
+    Log    ${normexp}
+    Create_File    ${ExpDir}${/}${name}    ${normexp}
+    ${resp}=    RequestsLibrary.Get    ses    topology/pcep-topology
+    Log    ${resp}
+    Log    ${resp.text}
+    ${normresp}=    Hsf_Json    ${resp.text}
+    Log    ${normresp}
+    Create_File    ${ActDir}${/}${name}    ${normresp}
+    Should_Be_Equal_As_Strings    ${resp.status_code}    200
+    Should_Be_Equal    ${normresp}    ${normexp}
+
+Tear_It_Down
+    [Documentation]    Download pccmock.log and Log its contents.
+    ...    Compute and Log the diff between expected and actual normalized responses.
+    ...    Close both HTTP client session and SSH connection to Mininet.
+    SSHLibrary.Get_File    pccmock.log
+    ${pccmocklog}=    Run    cat pccmock.log
+    Log    ${pccmocklog}
+    ${diff}=    Run    diff -dur ${ExpDir} ${ActDir}
+    Log    ${diff}
+    Delete_All_Sessions
+    Close_All_Connections
diff --git a/test/csit/testplans/bgpcep-basicpcep.txt b/test/csit/testplans/bgpcep-basicpcep.txt
new file mode 100644 (file)
index 0000000..cd77a26
--- /dev/null
@@ -0,0 +1,2 @@
+# Place the suites in run order:
+integration/test/csit/suites/bgpcep/basicpcep
diff --git a/test/csit/variables/basicpcep/variables.py b/test/csit/variables/basicpcep/variables.py
new file mode 100644 (file)
index 0000000..77b0aa5
--- /dev/null
@@ -0,0 +1,113 @@
+"""Variables file for basicpcep suite.
+
+Expected JSON templates are fairly long,
+therefore there are moved out of testcase file.
+Also, it is needed to generate base64 encoded tunnel name
+from Mininet IP (which is not known beforehand),
+so it is easier to employ Python here,
+than do manipulation in Robot file."""
+# Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+
+__author__ = "Vratko Polak"
+__copyright__ = "Copyright(c) 2015, Cisco Systems, Inc."
+__license__ = "Eclipse Public License v1.0"
+__email__ = "vrpolak@cisco.com"
+
+import binascii
+from string import Template
+
+
+def get_variables(mininet_ip):
+    """Return dict of variables for the given IP addtess of Mininet VM."""
+    tunnelname = 'pcc_' + mininet_ip + '_tunnel_1'
+    pathcode = binascii.b2a_base64(tunnelname)[:-1]  # remove endline
+    offjson = '''{
+ "topology": [
+  {
+   "topology-id": "pcep-topology",
+   "topology-types": {
+    "network-topology-pcep:topology-pcep": {}
+   }
+  }
+ ]
+}'''
+    onjsontempl = Template('''{
+ "topology": [
+  {
+   "node": [
+    {
+     "network-topology-pcep:path-computation-client": {
+      "ip-address": "$IP",
+      "reported-lsp": [
+       {
+        "name": "$NAME",
+        "path": [
+         {
+          "ero": {
+           "ignore": false,
+           "processing-rule": false,
+           "subobject": [
+            {
+             "ip-prefix": {
+              "ip-prefix": "1.1.1.1/32"
+             },
+             "loose": false
+            }
+           ]
+          },
+          "lsp-id": 1,
+          "odl-pcep-ietf-stateful07:lsp": {
+           "administrative": true,
+           "delegate": true,
+           "ignore": false,
+           "odl-pcep-ietf-initiated00:create": false,
+           "operational": "up",
+           "plsp-id": 1,
+           "processing-rule": false,
+           "remove": false,
+           "sync": true,
+           "tlvs": {
+            "lsp-identifiers": {
+             "ipv4": {
+              "ipv4-extended-tunnel-id": "$IP",
+              "ipv4-tunnel-endpoint-address": "1.1.1.1",
+              "ipv4-tunnel-sender-address": "$IP"
+             },
+             "lsp-id": 1,
+             "tunnel-id": 1
+            },
+            "symbolic-path-name": {
+             "path-name": "$CODE"
+            }
+           }
+          }
+         }
+        ]
+       }
+      ],
+      "state-sync": "synchronized",
+      "stateful-tlv": {
+       "odl-pcep-ietf-stateful07:stateful": {
+        "lsp-update-capability": true,
+        "odl-pcep-ietf-initiated00:initiation": true
+       }
+      }
+     },
+     "node-id": "pcc://$IP"
+    }
+   ],
+   "topology-id": "pcep-topology",
+   "topology-types": {
+    "network-topology-pcep:topology-pcep": {}
+   }
+  }
+ ]
+}''')
+    repl_dict = {'IP': mininet_ip, 'NAME': tunnelname, 'CODE': pathcode}
+    onjson = onjsontempl.substitute(repl_dict)
+    variables = {'offjson': offjson, 'onjson': onjson}
+    return variables