Skip to content

Commit 19e4347

Browse files
authored
Merge pull request #155 from dgarros/jsnapy_refactor
Jsnapy refactor proposal related to #154
2 parents 7763d2f + e221e7a commit 19e4347

File tree

3 files changed

+161
-16
lines changed

3 files changed

+161
-16
lines changed

README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,37 @@ Juniper Networks provides support for using Ansible to deploy devices running th
1616
- junos_srx_cluster — Enable/Disable cluster mode for SRX devices
1717
- junos_zeroize — Remove all configuration information on the Routing Engines and reset all key values on a device.
1818

19+
### OVERVIEW OF PLUGINS
20+
21+
In addition to the modules listed above, a callback_plugin `jsnapy` is available for the module `junos_jsnapy`.
22+
23+
The callback_plugin `jsnapy` helps to print on the screen additional information regarding
24+
jsnapy failed tests.
25+
For each failed test, a log will be printed after the RECAP of the playbook.
26+
27+
> The plugin `jsnapy` is currently in **Experimental** stage, please provide feedback.
28+
29+
```
30+
PLAY RECAP *********************************************************************
31+
qfx10002-01 : ok=3 changed=0 unreachable=0 failed=1
32+
qfx10002-02 : ok=3 changed=0 unreachable=0 failed=1
33+
qfx5100-01 : ok=1 changed=0 unreachable=0 failed=1
34+
35+
JSNAPy Results for: qfx10002-01 ************************************************
36+
Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "65200", "peer-state": "Active", "peer-address": "100.0.0.21"}
37+
Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "60021", "peer-state": "Idle", "peer-address": "192.168.0.1"}
38+
Value of 'oper-status' not 'is-equal' at '//interface-information/physical-interface[normalize-space(admin-status)='up' and logical-interface/address-family/address-family-name ]' with {"oper-status": "down", "name": "et-0/0/18"}
39+
40+
JSNAPy Results for: qfx10002-02 ************************************************
41+
Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "65200", "peer-state": "Active", "peer-address": "100.0.0.21"}
42+
```
43+
44+
Callback plugins are not activated by default and needs to be manually added to the
45+
configuration file under `[defaults]` with the variable `callback_whitelist`
46+
```
47+
[defaults]
48+
callback_whitelist = jsnapy
49+
```
1950
## DOCUMENTATION
2051

2152
[Official documentation](http://www.juniper.net/techpubs/en_US/release-independent/junos-ansible/information-products/pathway-pages/index.html) (detailed information, includes examples)
@@ -42,7 +73,7 @@ To download the junos role to the Ansible server, execute the ansible-galaxy ins
4273
For testing you can `git clone` this repo and run the `env-setup` script in the repo directory:
4374

4475
user@ansible-junos-stdlib> source env-setup
45-
76+
4677
This will set your `$ANSIBLE_LIBRARY` variable to the repo location and the installed Ansible library path. For example:
4778

4879
````
@@ -100,7 +131,7 @@ Thes modules require the following to be installed on the Ansible server:
100131
## LICENSE
101132

102133
Apache 2.0
103-
134+
104135
## CONTRIBUTORS
105136

106137
Juniper Networks is actively contributing to and maintaining this repo. Please contact [email protected] for any queries.

callback_plugins/jsnapy.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
2+
from __future__ import (absolute_import, division, print_function)
3+
__metaclass__ = type
4+
5+
import collections
6+
import os
7+
import time
8+
import pprint
9+
import json
10+
11+
from ansible.plugins.callback import CallbackBase
12+
from ansible import constants as C
13+
14+
class CallbackModule(CallbackBase):
15+
"""
16+
This callback add extra logging for the module junos_jsnapy .
17+
"""
18+
CALLBACK_VERSION = 2.0
19+
CALLBACK_TYPE = 'aggregate'
20+
CALLBACK_NAME = 'jsnapy'
21+
22+
## useful links regarding Callback
23+
## https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/__init__.py
24+
25+
def __init__(self):
26+
self._pp = pprint.PrettyPrinter(indent=4)
27+
self._results = {}
28+
29+
super(CallbackModule, self).__init__()
30+
31+
def v2_runner_on_ok(self, result):
32+
"""
33+
Collect test results for all tests executed if module is junos_jsnapy
34+
"""
35+
36+
## Extract module name
37+
module_name = ''
38+
if 'invocation' in result._result:
39+
if 'module_name' in result._result['invocation']:
40+
module_name = result._result['invocation']['module_name']
41+
42+
if module_name == 'junos_jsnapy':
43+
## Check if dict entry already exist for this host
44+
host = result._host.name
45+
if not host in self._results.keys():
46+
self._results[host] = []
47+
48+
self._results[host].append(result)
49+
50+
def v2_playbook_on_stats(self, stats):
51+
52+
## Go over all results for all hosts
53+
for host, results in self._results.iteritems():
54+
has_printed_banner = False
55+
for result in results:
56+
# self._pp.pprint(result.__dict__)
57+
res = result._result
58+
if res['final_result'] == "Failed":
59+
for test_name, test_results in res['test_results'].iteritems():
60+
for testlet in test_results:
61+
if testlet['count']['fail'] != 0:
62+
63+
if not has_printed_banner:
64+
self._display.banner("JSNAPy Results for: " + str(host))
65+
has_printed_banner = True
66+
67+
for test in testlet['failed']:
68+
self._display.display(
69+
"Value of '{0}' not '{1}' at '{2}' with {3}".format(
70+
str(testlet['node_name']),
71+
str(testlet['testoperation']),
72+
str(testlet['xpath']),
73+
json.dumps(test['post'])),
74+
color=C.COLOR_ERROR)

library/junos_jsnapy

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,22 @@
3636
DOCUMENTATION = '''
3737
---
3838
module: junos_jsnapy
39-
author: Roslan Zaki, Juniper Networks
40-
version_added: "1.0.0"
39+
author: Roslan Zaki & Damien Garros, Juniper Networks
40+
version_added: "1.4.0"
4141
version_control:
4242
13-Apr-2016 v1.0.0 - Basic working model
4343
44-
Short_description: Integrate JSnapy to ansible
44+
Short_description: Integrate JSnapy to ansible.
4545
description:
46-
- Execute JSnapy module from Ansible
46+
- Execute JSnapy test from Ansible.
47+
Attention, to not break Ansible behavior, this module only report "failed"
48+
if the module itself fails, not if a test fails.
49+
To check the test results you need to subscribe to the result and assert
50+
the returned value.
51+
An experimental Callback_Plugin for junos_jsnapy is available to provide
52+
additional information about tests that failed.
53+
To enable it, you need to add "callback_whitelist = jsnapy" in your ansible
54+
configuration file.
4755
requirements:
4856
- junos-eznc >= 1.2.2
4957
options:
@@ -114,20 +122,41 @@ options:
114122
EXAMPLES = '''
115123
- name: JUNOS Post Checklist
116124
junos_jsnapy:
117-
host: "{{ inventory_hostname}}"
118-
passwd: "{{ tm1_password }}"
119-
action: "snap_post"
120-
config_file: "first_test.yml"
121-
logfile: "migration_post.log"
122-
register: jsnapy
125+
host: "{{ inventory_hostname}}"
126+
passwd: "{{ tm1_password }}"
127+
action: "snap_post"
128+
config_file: "first_test.yml"
129+
logfile: "migration_post.log"
130+
register: test1
131+
132+
- name: Check JSNAPy tests results
133+
assert:
134+
that:
135+
- "test1.passPercentage == 100"
123136
124137
- name: Debug jsnapy
125-
debug: msg="{{jsnapy}}"
138+
debug: msg=test1
139+
140+
---------
141+
- name: Test based on a test_file directly
142+
junos_jsnapy:
143+
host: "{{ junos_host }}"
144+
port: "{{ netconf_port }}"
145+
user: "{{ ansible_ssh_user }}"
146+
passwd: "{{ ansible_ssh_pass }}"
147+
test_files: tests/test_junos_interface.yaml
148+
action: snapcheck
149+
register: test1
126150
151+
- name: Check JSNAPy tests results
152+
assert:
153+
that:
154+
- "test1.passPercentage == 100"
127155
'''
128156
from distutils.version import LooseVersion
129157
import logging
130158
from lxml.builder import E
159+
import os.path
131160
import os
132161
import time
133162

@@ -153,6 +182,13 @@ def jsnap_selection(dev, module):
153182
config_data = os.path.join(config_dir, config_file)
154183
else:
155184
test_files = args.get('test_files')
185+
186+
for test_file in test_files:
187+
if not os.path.isfile(test_file):
188+
msg = 'unable to find the test file {0}'.format(test_file)
189+
logging.error(msg)
190+
module.fail_json(msg=msg)
191+
156192
config_data = {'tests': test_files}
157193

158194
results = {}
@@ -167,6 +203,7 @@ def jsnap_selection(dev, module):
167203
elif action == 'check':
168204
snapValue = js.check(data=config_data, dev=dev, pre_file='PRE', post_file='POST')
169205

206+
percentagePassed = 0
170207
if isinstance(snapValue, (list)):
171208
for snapCheck in snapValue:
172209
router = snapCheck.device
@@ -177,7 +214,10 @@ def jsnap_selection(dev, module):
177214
results['test_results'] = snapCheck.test_results
178215
total_test = int(snapCheck.no_passed) + int(snapCheck.no_failed)
179216
results['total_tests'] = total_test
180-
percentagePassed = (int(results['total_passed']) * 100 ) / (results['total_tests'])
217+
218+
if total_test != 0:
219+
percentagePassed = (int(results['total_passed']) * 100 ) / (results['total_tests'])
220+
181221
results['passPercentage'] = percentagePassed
182222

183223
return results
@@ -203,7 +243,7 @@ def main():
203243
args = module.params
204244
results = {}
205245

206-
if import_err_message is not None:
246+
if import_err_message is not None:
207247
module.fail_json(msg=import_err_message)
208248

209249
if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'):
@@ -236,7 +276,7 @@ def main():
236276
)
237277
logging.error(msg)
238278
dev.close()
239-
module.fail_json(msg=msg, **data)
279+
module.exit_json(msg=msg, **data)
240280

241281
except Exception as err:
242282
msg = 'Uncaught exception - please report: {0}'.format(str(err))

0 commit comments

Comments
 (0)