--- /dev/null
+== Backup-Restore test support library ==
+
+=== 1. Introduction ===
+
+The purpose of this library is to allow the generic verification
+of feature correctness in backup + restore scenarios.
+
+A feature is correct from a backup+restore standpoint when,
+at any provisioning point, a controller backup, followed by a
+restore, can be performed, and the execution of that procedure
+will not have any impact on traffic tests or datastore state
+check in respect to the expected behaviour when no backup + restore
+procedures are performed.
+
+The library can also be used (with minimal modifications) to check
+whether a feature is safe (continues operating correctly) in the
+event of a controller reboot (e.g. SFC is known not to, because of
+keeping certain information (rendered service paths) in the
+operational DS only and being unable to reconstruct that information
+after a reboot).
+
+=== 2. Library usage ===
+
+==== 2.1. Use as Robot Library keywords ====
+
+The library is delivered as a readily-available Robot FW library in
+the ODL integration/test repository. It provides two keywords:
+
+- A new keyword ('''BackupRestoreCheck'''), which:
+
+# Performs a complete datastore export (using Daexim export rpc)
+# Does a backup, then a restore of the backup previously
+ created. NOTE: this step is purposefully not implemented in the
+ keyword (a placeholder for concrete backup & restore scripts is
+ provided instead). ODL does not provide a comprehensive B&R implementation:
+ such implementation shall include both the datastore and certain
+ configuration files, but those configuration files depend on the
+ concrete ODL distribution / deployment, so ODL provides only some
+ pieces to implement that backup (i.e. the datastore backup RPCs).
+ This library is contributed with the purpose of easing the testing
+ of any backup&restore implementation; therefore that implementation
+ shall be incorporated to this library (by modifying this step)
+# Performs a new datastore export
+# Compares both config & operational datastores for differences (that
+ is, pre-backup and post-restore exports for both datastores), optionally
+ prefiltering those exports using pre-filter files (those prefilter files
+ are passed as parameters to the keyword)
+# Fails when pre-backup and post-restore exports are different even after
+ removing the specified filtered parts
+
+- A new keyword ('''ConditionalBackupRestoreCheck'''), which performs
+ the same steps than BackupRestoreCheck only when a command-line flag ("-v
+ BR_TESTING_ENABLED:true") is present. This allows to easily add
+backup-restore verification on existing tests, allowing to toggle
+the execution of that verification
+
+===== 2.1.1. Adding the br verification keyword to an existing robot test =====
+
+The library has been designed from the ground up to allow its use in
+existing testcases, so specific feature provisioning can be tested for
+correctness in backup-restore scenarios. The design premises for the
+library were:
+* To be very easy to incorporate into existing testcases (just one resource
+ import + the verification keyword, that shall be inserted in the existing
+ testcase just after test specific provisioning and before existing test
+verification steps)
+* To be togglable (that is, to allow whether to execute / to bypass the
+export + backup + restore + export + exports comparison block)
+
+===== 2.1.2. Steps to add backup-restore verification to an existing test suite =====
+1) Suite setup: Add ClusterManagement Setup (it is needed for Daexim export
+ to work). Hint: if the testsuite already contains an init suite keyword, you
+ can use the Run Keywords construct in order to run both initialization keywords.
+
+(Subsequent examples use diff-format):
+
+*** Settings ***
+Documentation Test suite for SFC Service Functions, Operates functions from Restconf APIs.
+-Suite Setup Init Suite
++Suite Setup Run Keywords Init Suite ClusterManagement Setup
+Suite Teardown Delete All Sessions
+
+2) Import Backup-Restore support library
+
+Resource ../../../libraries/TemplatedRequests.robot
++Resource ../../../libraries/BackupRestoreKeywords.robot
+
+3) Add the verification keyword (after provisioning, before assertions/traffic verification)
+Should Contain ${ALLOWED_STATUS_CODES} ${resp.status_code}
+ ${elements}= Create List SFC1-100-Path-1 "parent-service-function-path":"SFC1-100" "hop-number":0 "service-index":255 "ho
+... "service-index":254 "hop-number":2 "service-index":253
++ ConditionalBackupRestoreCheck
+Check For Elements At URI ${OPERATIONAL_RSPS_URI} ${elements}
+
+4) Execute the suite without passing the enablement flag (or pass it disabled:
+ '-v BR_TESTING_ENABLED:false'). Note how the testcase runs as always
+5) Execute the suite, now passing the enablement flag ('-v BR_TESTING_ENABLED:true'):
+5.1. If the testcases pass, that means the suite provisioning is safe for B&R
+ (that is, both config and operational DSs are identical before and after the
+ procedure, and any assert / traffic verification the cases perform are also correct.
+5.2. If a testcase fail:
+5.2.1. If it is the ConditionalBackupRestoreCheck keyword what fails: it means
+ differences are found in the datastores before the backup / after the restore. Test
+log should include the list of differences found. Two types of differences:
+5.2.1.1. If differences are non-issues (e.g. elements whose changes are expected
+ after a backup + restore, as elements containing timestamps that are recalculated
+ after the restore, or elements showing transitory states which are not important
+ regarding B&R correctness), then create as many pre-filter entries as necessary
+ in the corresponding prefilter file (4 prefilter files can be passed to the
+ keyword: prefilter for the config DS before the backup, config DS after restore,
+ operational DS before the backup and operational DS after the restore. Repeat until
+ all unimportant DS entries are filtered
+5.2.1.2. Differences found on which the former rule is not applicable should be
+ checked carefully, as they are likely to showcase application bugs (regading B&R /
+ controller reboots)
+5.2.2 Errors in the testcase execution when the BR_TESTING_ENABLED:true flag is
+ passed, in keywords other than the verification keyword, are also candidates to
+ point to application bugs (e.g. because of using runtime-required in-memory
+ state that they fail to reconstruct after the restore), thus requiring careful revision
+
+==== 2.2. Execution as a standalone commandline utility ====
+In scenarios where Robot FW is not used for testing, the library core (this is, the
+prefiltered json comparison) can also be used from the command line. The tool is
+provided as a python commandline utility. Help follows:
+
+ odluser@odluser-VirtualBox:~/odl/test/csit/libraries/backuprestore\> python JsonDiffTool.py -h
+ usage: JsonDiffTool.py [-h] -i INITIALFILE -f FINALFILE [-ipf INITIALPREFILTER] [-fpf FINALPREFILTER] [-pd] [-v]
+ both initial and final json files are compared for differences. The program
+ returns 0 when the json contents are the same, or the number of differences
+ otherwise. Both json files can be prefiltered for certain patterns before
+ checking the differences
+ optional arguments:
+ -h, --help show this help message and exit
+ -i INITIALFILE, --initialFile INITIALFILE
+ initial json file
+ -f FINALFILE, --finalFile FINALFILE
+ final json file
+ -ipf INITIALPREFILTER, --initialPreFilter INITIALPREFILTER
+ File with pre-filtering patterns to apply to the
+ initial json file before comparing
+ -fpf FINALPREFILTER, --finalPreFilter FINALPREFILTER
+ File with pre-filtering patterns to apply to the final
+ json file before comparing
+ -pd, --printDifferences
+ on differences found, prints the list of paths for the
+ found differences before exitting
+ -v, --verbose generate log information
+
+===== 2.2.1. Command-line usage examples =====
+- Checking for differences between two json files (showing only the number of differences)
+ odluser@odluser-VirtualBox:~/odl/test/csit/libraries/backuprestore\> python JsonDiffTool.py -i ./testinput/arrayTwoNames.json -f ./testinput/arrayThreeNamesSorted.json
+ 1
+
+- Checking for differences and displaying the differences (jsonpatch format)
+ odluser@odluser-VirtualBox:~/odl/test/csit/libraries/backuprestore\> python JsonDiffTool.py -i ./testinput/arrayTwoNames.json -f ./testinput/arrayThreeNamesSorted.json -pd
+ {"path": "/2", "value": {"Name": "Tom"}, "op": "add"}
+ 1
+
+- Checking for differences (and displaying them), using a pre-filter file for the initial json file
+ odluser@odluser-VirtualBox:~/odl/test/csit/libraries/backuprestore\> python JsonDiffTool.py -i ./testinput/mainTestCase/odl_backup_operational_before.json -f testinput/mainTestCase/odl_backup_operational_after.json -ipf testinput/mainTestCase/json_prefilter.conf -pd
+ {"path": "/entity-owners:entity-owners/entity-type/2", "op": "remove"}
+ {"path": "/entity-owners:entity-owners/entity-type/4", "value": {"type": "iface", "entity": [{"owner": "member-1", "id": "/general-entity:entity[general-entity:name='iface']", "candidate": [{"name": "member-1"}]}]}, "op": "add"}
+ {"path": "/network-topology:network-topology/topology/3", "value": {"node": [{"netconf-node-topology:host": "127.0.0.1", "netconf-node-topology:port": 1830, "netconf-node-topology:connection-status": "connecting", "node-id": "CONTROLLER1"}, {"netconf-node-topology:host": "127.0.0.1", "netconf-node-topology:port": 1830, "netconf-node-topology:connection-status": "connecting", "node-id": "CONTROLLER2"}], "topology-id": "topology-netconf"}, "op": "replace"}
+ {"path": "/ietf-yang-library:modules-state/module-set-id", "value": "3", "op": "replace"}
+ {"path": "/ietf-yang-library:modules-state/module/56", "op": "remove"}
+ {"path": "/ietf-yang-library:modules-state/module/116", "op": "remove"}
+ {"path": "/ietf-yang-library:modules-state/module/127", "op": "remove"}
+ {"path": "/ietf-yang-library:modules-state/module/140", "op": "remove"}
+ {"path": "/ietf-yang-library:modules-state/module/139", "op": "remove"}
+ {"path": "/ietf-yang-library:modules-state/module/185", "op": "remove"}
+ {"path": "/ietf-yang-library:modules-state/module/238", "op": "remove"}
+ {"path": "/ietf-yang-library:modules-state/module/267", "op": "remove"}
+ {"path": "/ietf-yang-library:modules-state/module/269", "op": "remove"}
+ {"path": "/ietf-yang-library:modules-state/module/278", "op": "remove"}
+ {"path": "/ietf-yang-library:modules-state/module/277", "op": "remove"}
+ 15
+
+===== 2.2.2. Unit tests =====
+A handful of unit tests (testing both jsonpath - jsonpatch expression transformation,
+difference evaluation, use of filters and error cases) are provided. They can be
+executed as standard python unittests from the commandline:
+
+ odluser@odluser-VirtualBox:~/odl/test/csit/libraries/backuprestore\> python backuprestoretest.py
+ 0
+ .2
+ .usage: backuprestoretest.py [-h] -i INITIALFILE -f FINALFILE
+ [-ipf INITIALPREFILTER] [-fpf FINALPREFILTER]
+ [-pd] [-v]
+ backuprestoretest.py: error: argument -i/--initialFile is required
+ .14
+ 14
+ .16
+ 16
+ .16
+ 16
+ .1
+ ..16
+ 16
+ ...
+ ----------------------------------------------------------------------
+ Ran 11 tests in 0.881s
+ OK
+
+=== 3. Prefilter file format ===
+Prefilter files:
+* Can contain any number of jsonpath expressions ([http://goessner.net/articles/JsonPath/ jsonpath expressions specification])
+* Use "#" as line prefix for comments
+Example:
+ #
+ # Pre-filter file example (removes the module from ietf-yang-library:modules-state which name is 'extension-resync-message')
+ #
+ $.ietf-yang-library:modules-state.module[?(@.name=='extension-resync-message')]
+ # $.ietf-yang-library:modules-state.module[?(@.name=='extension-switchfeatures-message')]
+
+=== 4. Dependencies / discarded alternatives ===
+
+The library includes in the commit itself the jsonpath library by Phil Budne
+(https://pypi.python.org/pypi/jsonpath/). This had to be done in order to rename
+the module (from jsonpath to jsonpathl), because RIDE fails to import a class
+from a module with the same name, which is the case for this library. The library
+license (MIT) allows for including / modifying it, so this inclusion is safe license-wise.
+
+Other alternatives to this library were explored, but were found unfit for the
+purpose. Specifically, we tried to use the popular jsonpath-rw library, but it
+does not support json query filtering by attribute values (only by field names),
+which is a must. Also objectpath, but the library does not support the return of
+the path of matching objects (only matched objects themselves). Those paths are
+required (they are the ones which are transformed into jsonpatch expressions)
+
+jsonpatch library (https://pypi.python.org/pypi/jsonpatch) is expected to be
+installed in order for this library to work. The library is used for removing json
+elements via patches