== 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