Skip to content

4. Module

sidhujasminder edited this page Jun 16, 2017 · 17 revisions

JSNAPy as module

For using it as module, it provides following functions under class Jsnapy:

  1. snap(config_file/config_data, snap_file_name, dev, folder)
    This function lets you take snapshot.

    snap_file_name : User can give either 
                     - full file path (but file should exists) 
                     - prefix, in this case file name is formed automatically (<devicename>_<prefix>_<command/rpc>.<xml/text>)
                       all snapshots are taken from '/etc/jsnapy/snapshots' unless complete path of snapshot file is specified.
    
    config_file : User can give either full path or name of config file, in that case it should be present in current working directory. Inside this config file, user can give test files by giving:
                  - full path of test file 
                  - only name of test file, this file should be present in '/etc/Jsnapy/testfiles'. 
    
    config_data : Instead of test details in config file, user can also specify yaml content in some variable. It can also be passed as a dictionary containing test details. For example refer sample program 4.
    
    dev : PyEZ device object, by default it is None 
          If device object is given then it will connect to given device otherwise take device details from config file. 
    
    folder : Use this command to specify custom jsnapy lookup directory. For more info refer [here](https://github.com/Juniper/jsnapy/wiki/3.-Command-Line-Tool#optional-arguments) 
    
    snap function return list of objects. Each object is associated per device (as included in config file, if the config file got one device detail only, list will contain 1 element only). Each object has attributes rpc_list, command_list, test_included to give details of snapshots being taken.
    

    Note:
    PyEZ
    folder: Added in v1.1

  2. snapcheck (config_file_name/ config_yaml_data, snap_file_name, dev, local, folder)
    This function compares the current configuration against pre-defined criteria.

    local : Boolean. Pass `True` to run snapcheck on local snapshot(s). Behaviour is same as that of command line param mentioned [here](https://github.com/Juniper/jsnapy/wiki/3.-Command-Line-Tool#optional-arguments) 
    rest parameters mean same as snap function <br>
    
    snapcheck function return list of objects. Each object is associated per device. Each object has attributes device, result, no_passed, no_failed, test_results to give details of results.
    
  3. check (config_file_name, snap_filename1, snap_filename2, dev, folder)
    This function compares two snapshots based on given test cases.
    parameters mean same as snap function

    check function return list of objects. Each object is associated per device. Each object has attributes device, result, no_passed, no_failed, test_results to give details of results.
    

*Work of functions snap, check, snapcheck is same as mentioned in command line argument.

Output format:

  • Default : A list of jsnapy.Operator objects. Each Operator object has a test_results dictionary which contains all details about total number of test cases passed/failed.
  • Using local=True : Added in v1.1 In this case the snapcheck function returns a list of dictionary, where the dictionary is keyed with the various snapshot names passed and the value is an Operator object which contains the test details.

Sample Program:

1] Using JSNAPy as a module:

### Performing function similar to --check in command line ###
from jnpr.jsnapy import SnapAdmin
from pprint import pprint
from jnpr.junos import Device

js = SnapAdmin()

config_file = "/etc/jsnapy/testfiles/demo_check.yml"
js.snap(config_file, "pre")
js.snap(config_file, "post")
chk = js.check(config_file, "pre", "post")

for check in chk:
    print "Tested on", check.device
    print "Final result: ", check.result
    print "Total passed: ", check.no_passed
    print "Total failed:", check.no_failed
    pprint(check.test_results)

/etc/jsnapy/testfiles/demo_check.yml

# for one device, can be given like this:
hosts:
  - device: device
    username : foo
    passwd: bar
tests:
 - test_no_diff.yml

test_no_diff.yml

test_command_version:
  - command: show interfaces terse lo* 
  - iterate:
      xpath: physical-interface
      id: ['./name']
      tests:
        - no-diff: admin-status       # element in which test is performed
          err: "Failed!! admin-status  got changed, PRE: <{{pre['admin-status']}}>, POST: <{{post['admin-status']}}>"
          info: "Passed!! admin-status is same PRE: <{{pre['admin-status']}}> POST: <{{post['admin-status']}}>"

test_command_version:
  - command: show interfaces terse
  - iterate:
      xpath: physical-interface
      id: ['./name']
      tests:
        - no-diff: admin-status       # element in which test is performed
          err: "Failed!! admin-status  got changed, PRE: <{{pre['admin-status']}}>, POST: <{{post['admin-status']}}>"
          info: "Passed!! admin-status is same PRE: <{{pre['admin-status']}}> POST: <{{post['admin-status']}}>"

Output:

Tested on device
Final result:  Failed
Total passed:  0
Total failed: 1
{'show interfaces terse': [{'count': {'fail': 3, 'pass': 53},
                            'failed': [{'id_missing_pre': {'./name': 'demux0'}},
                                       {'id_missing_post': {'./name': 'demux01'}},
                                       {'id': {'./name': 'cbp0'},
                                        'post': {'admin-status': ['up']},
                                        'post_node_value': ['up'],
                                        'pre': {'admin-status': ['down']},
                                        'pre_node_value': ['down']}],
                            'node_name': 'admin-status',
                            'passed': [{'id': {'./name': 'ge-2/2/3'},
                                        'post': {'admin-status': ['up']},
                                        'post_node_value': ['up'],
                                        'pre': {'admin-status': ['up']},
                                        'pre_node_value': ['up']},
                                        .............
                                        .......
                                       {'id': {'./name': 'ge-2/2/4'},
                                        'post': {'admin-status': ['up']},
                                        'post_node_value': ['up'],
                                        'pre': {'admin-status': ['up']},
                                        'pre_node_value': ['up']},
                            'result': False,
                            'testoperation': 'no-diff',
                            'xpath': 'physical-interface'}]}

2] Here, I am not creating new device object. Reusing already existing PyEZ device object to take snapshot:

from jnpr.jsnapy import SnapAdmin
from pprint import pprint
from jnpr.junos import Device

dev_obj = Device(host='10.209.1.1', user='foo', password='bar')
dev_obj.open()

js = SnapAdmin()
config_file = "/etc/jsnapy/config_snapcheck.yml"
snapvalue = js.snapcheck(config_file, "pre", dev=dev_obj)

for snapcheck in snapvalue:
    print "\n -----------snapcheck----------"
    print "Tested on", snapcheck.device
    print "Final result: ", snapcheck.result
    print "Total passed: ", snapcheck.no_passed
    print "Total failed:", snapcheck.no_failed
    pprint(snapcheck.test_results)  

/etc/jsnapy/config_snapcheck.yml

hosts:
  - device: device
    username : foo
    passwd: bar
tests:
  - test_is_equal.yml
  - test_not_in.yml

test_is_equal.yml

test_interfaces_terse:
  - command: show interfaces terse lo*  
  - iterate:
      id: ./name
      xpath: //physical-interface
      tests:
        - is-equal: admin-status, up
          info: "Test Succeeded !! admin-status of interface <{{post['name']}}> is equal to <{{post['admin-status']}}> with oper-status <{{ pre['oper-status'] }}>"
          err: "Test Failed !! admin-status of interface <{{ post['name'] }}> is not equal to down, it is <{{post['admin-status']}}> with oper-status <{{pre['oper-status']}}>"

test_not_in.yml

check_show_interfaces_terse:
  - command: show interfaces terse
  - iterate:
      xpath: //physical-interface
      tests:
        - not-in: oper-status, down, up
          info: "Test Succeeded!! Physical operational status is-in down-up, it is: <{{post['oper-status']}}> with admin status <{{post['admin-status']}}>"
          err: "Test Failed!!! Physical operational status is not in downoo-up, it is: <{{post['oper-status']}}> with admin status <{{post['admin-status']}}> "

Sample Output:

Tested on 10.209.1.1
Final result:  Failed
Total passed:  1
Total failed: 1
{'show interfaces terse': [{'count': {'fail': 17, 'pass': 0},
                            'expected_node_value': ['down', 'up'],
                            'failed': [{'actual_node_value': 'up',
                                        'id': {},
                                        'post': {'admin-status': 'up',
                                                 'oper-status': 'up'},
                                        'pre': {'oper-status': 'up'}},
                                       {'actual_node_value': 'up',
                                        'id': {},
                                        'post': {'admin-status': 'up',
                                                 'oper-status': 'up'},
                                        'pre': {'oper-status': 'up'}},
                                       ............
                                       {'actual_node_value': 'up',
                                        'id': {},
                                        'post': {'admin-status': 'up',
                                                 'oper-status': 'up'},
                                        'pre': {'oper-status': 'up'}}],
                            'node_name': 'oper-status',
                            'passed': [],
                            'result': False,
                            'testoperation': 'not-in',
                            'xpath': '//physical-interface'}],
 'show interfaces terse lo*': [{'count': {'fail': 0, 'pass': 1},
                                'expected_node_value': 'up',
                                'failed': [],
                                'node_name': 'admin-status',
                                'passed': [{'actual_node_value': 'up',
                                            'id': {'./name': 'lo0'},
                                            'post': {'admin-status': 'up',
                                                     'name': 'lo0'},
                                            'pre': {'admin-status': 'up',
                                                    'oper-status': 'up'}}],
                                'result': True,
                                'testoperation': 'is-equal',
                                'xpath': '//physical-interface'}]}

3] Instead of passing config file in yaml, you can pass file details as yaml data also.

### Example showing how to pass yaml data in same file ###
from jnpr.jsnapy import SnapAdmin
from pprint import pprint
from jnpr.junos import Device

js = SnapAdmin()

config_data = """
hosts:
  - device: 1.1.1.1
    username : demo 
    passwd: demo123
tests:
  - test_is_equal.yml
"""

snapchk = js.snapcheck(config_data, "pre")
for val in snapchk:
    print "Tested on", val.device
    print "Final result: ", val.result
    print "Total passed: ", val.no_passed
    print "Total failed:", val.no_failed
    pprint(val.test_results)

Output:

Tested on 1.1.1.1
Final result:  Passed
Total passed:  1
Total failed: 0
{'show interfaces terse lo*': [{'count': {'fail': 0, 'pass': 1},
                                'expected_node_value': 'up',
                                'failed': [],
                                'node_name': 'admin-status',
                                'passed': [{'actual_node_value': 'up',
                                            'id': {'./name': 'lo0'},
                                            'post': {'admin-status': 'up',
                                                     'name': 'lo0'},
                                            'pre': {'admin-status': 'up',
                                                    'oper-status': 'up'}}],
                                'result': True,
                                'testoperation': 'is-equal',
                                'xpath': '//physical-interface'}]}

4] Passing test details as dictionary to functions

### Example showing how to pass test details directly to function ###
from jnpr.jsnapy import SnapAdmin
from pprint import pprint
from jnpr.junos import Device

    js = SnapAdmin()
    snap = js.snap({'tests': ['test_is_equal_iter.yml', 'test_route_summary.yml']}, 'PRE', dev=dev)
    snap = js.snap({'tests': ['test_is_equal_iter.yml', 'test_route_summary.yml']}, 'POST', dev=dev)
    chk = js.check({'tests': ['test_is_equal_iter.yml', 'test_route_summary.yml']}, "PRE", "POST", dev=dev)

    for checkvalue in chk:
        print "\n -----------check----------"
        print "Tested on", checkvalue.device
        print "Final result: ", checkvalue.result
        print "Total passed: ", checkvalue.no_passed
        print "Total failed:", checkvalue.no_failed
        pprint(checkvalue.test_results)

5] Return value from snap can give details of rpc/commands executed and test included from the config file.

### Example showing how to pass test details directly to function ###
from jnpr.jsnapy import SnapAdmin
from pprint import pprint
from jnpr.junos import Device

    js = SnapAdmin()
    snap = js.snap('/home/user/config.yml', 'PRE')
    for item in snap:
        print item.test_included
        print item.rpc_list
        print item.command_list

6] JSNAPy results can now also be obtained with testname as key in output dictionary

Two Properties are defined, user can use as per need

  • test_results: will provide output dictionary with command as keys
  • testname_results: will provide output dictionary with testname as keys Added in v1.2
from jnpr.jsnapy import SnapAdmin
from pprint import pprint

js = SnapAdmin()
pre_pass = js.snap('config.yml', 'pre_check')
post_pass = js.snap('config.yml', 'post_check')
snapval = js.check('config.yml', 'pre_check', 'post_check')

for item in snapval:
    print("\n -----------snapcheck----------")
    print("Tested on", item.device)
    print("Final result: ", item.result)
    print("Total passed: ", item.no_passed)
    print("Total failed:", item.no_failed)
    pprint(item.test_results)
    pprint(item.testname_results)

config.yml

hosts:
  - device: 1.1.1.1
    username: abc
    passwd: xyz

tests:
  - test_in_range.yml

test_in_range.yml

check_chassis_fpc:
  - command: show chassis fpc
  - iterate:
      xpath: fpc[normalize-space(slot) = "0"]
      tests:
       - in-range: memory-heap-utilization, 5, 40
         info: "Test Succeeded!! memory heap utilisation of the FPCs is within the range of 5-40, it is <{{post['memory-heap-utilization']}}> with temperature: <{{post['temperature']}}>"
         err: "Test Failed!! memory heap utilisation of the FPCs is not in range of 5-40, it is <{{post['memory-heap-utilization']}}> with temperature: <{{post['temperature']}}>"

Final Output

 -----------snapcheck----------
('Tested on', '1.1.1.1')
('Final result: ', 'Passed')
('Total passed: ', 1)
('Total failed:', 0)
{'show chassis fpc': [{'count': {'fail': 0, 'pass': 1},
                       'expected_node_value': [5.0, 40.0],
                       'failed': [],
                       'node_name': 'memory-heap-utilization',
                       'passed': [{'actual_node_value': '20',
                                   'id': {},
                                   'message': 'Test Succeeded!! memory heap utilisation of the FPCs is within the range of 5-40, it is <20> with temperature: <Testing>',
                                   'post': {'memory-heap-utilization': '20',
                                            'temperature': 'Testing'},
                                   'pre': {'memory-heap-utilization': '20'}}],
                       'result': True,
                       'test_name': 'check_chassis_fpc',
                       'testoperation': 'in-range',
                       'xpath': 'fpc[normalize-space(slot) = "0"]'}]}
{'check_chassis_fpc': [{'command': 'show chassis fpc',
                        'count': {'fail': 0, 'pass': 1},
                        'expected_node_value': [5.0, 40.0],
                        'failed': [],
                        'node_name': 'memory-heap-utilization',
                        'passed': [{'actual_node_value': '20',
                                    'id': {},
                                    'message': 'Test Succeeded!! memory heap utilisation of the FPCs is within the range of 5-40, it is <20> with temperature: <Testing>',  
                                    'post': {'memory-heap-utilization': '20',
                                             'temperature': 'Testing'},
                                    'pre': {'memory-heap-utilization': '20'}}],
                        'result': True,
                        'testoperation': 'in-range',
                        'xpath': 'fpc[normalize-space(slot) = "0"]'}]}

Added in v1.2 Note: The message field in the output, under the key passed or failed is the err/info string. Depending on the output, the message field will be in either of them. The message will contain the value of the nodes defined in testfiles err/info message using the jinja template(<{{post['temperature']}}>). The message string will contain the value of 'temperature' from post snapshot. Look in the above example for reference.

Note: To hide messages coming in console, apart from explicitly given print statements, please remove console file handler from list of handlers in /etc/jsnapy/logging.yml

(venv)sh-3.2# cat /etc/jsnapy/logging.yml 
version: 1

disable_existing_loggers: True 

## use formatters to cutomize your output
## add of remove parameters accordingly
##

formatters:
    custom_format:
        format: "%(asctime)s - %(name)s - %(levelname)s - %(hostname)s ............. \n %(message)s"
    simple:
        format: "%(hostname)s-- %(message)s"
    default:
        format: "%(message)s"
    default_file:
        format:  "%(asctime)s - %(name)s - %(levelname)s - \n %(message)s"
handlers:
    console:
        class: logging.StreamHandler
        level: INFO 
        formatter: default
        stream: ext://sys.stdout
.......
....

root:
    level: DEBUG 
    handlers: [debug_file_handler]  ## removed console option from here
Clone this wiki locally