1#!/usr/bin/python
2#
3# This file is part of Ansible
4#
5# Ansible is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# Ansible is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
17#
18
19ANSIBLE_METADATA = {'metadata_version': '1.1',
20                    'status': ['preview'],
21                    'supported_by': 'community'}
22
23
24DOCUMENTATION = '''
25---
26module: nxos_ping
27extends_documentation_fragment: nxos
28version_added: "2.1"
29short_description: Tests reachability using ping from Nexus switch.
30description:
31    - Tests reachability using ping from switch to a remote destination.
32    - For a general purpose network module, see the M(net_ping) module.
33    - For Windows targets, use the M(win_ping) module instead.
34    - For targets running Python, use the M(ping) module instead.
35author:
36    - Jason Edelman (@jedelman8)
37    - Gabriele Gerbino (@GGabriele)
38options:
39    dest:
40        description:
41            - IP address or hostname (resolvable by switch) of remote node.
42        required: true
43    count:
44        description:
45            - Number of packets to send.
46        default: 5
47    source:
48        description:
49            - Source IP Address or hostname (resolvable by switch)
50    vrf:
51        description:
52            - Outgoing VRF.
53    state:
54        description:
55            - Determines if the expected result is success or fail.
56        choices: [ absent, present ]
57        default: present
58notes:
59    - For a general purpose network module, see the M(net_ping) module.
60    - For Windows targets, use the M(win_ping) module instead.
61    - For targets running Python, use the M(ping) module instead.
62'''
63
64EXAMPLES = '''
65- name: Test reachability to 8.8.8.8 using mgmt vrf
66  nxos_ping:
67    dest: 8.8.8.8
68    vrf: management
69    host: 68.170.147.165
70
71- name: Test reachability to a few different public IPs using mgmt vrf
72  nxos_ping:
73    dest: nxos_ping
74    vrf: management
75    host: 68.170.147.165
76  with_items:
77    - 8.8.8.8
78    - 4.4.4.4
79    - 198.6.1.4
80'''
81
82RETURN = '''
83commands:
84    description: Show the command sent
85    returned: always
86    type: list
87    sample: ["ping 8.8.8.8 count 2 vrf management"]
88rtt:
89    description: Show RTT stats
90    returned: always
91    type: dict
92    sample: {"avg": 6.264, "max": 6.564, "min": 5.978}
93packets_rx:
94    description: Packets successfully received
95    returned: always
96    type: int
97    sample: 2
98packets_tx:
99    description: Packets successfully transmitted
100    returned: always
101    type: int
102    sample: 2
103packet_loss:
104    description: Percentage of packets lost
105    returned: always
106    type: str
107    sample: "0.00%"
108'''
109from ansible.module_utils.network.nxos.nxos import run_commands
110from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, check_args
111from ansible.module_utils.basic import AnsibleModule
112
113
114def get_summary(results_list, reference_point):
115    summary_string = results_list[reference_point + 1]
116    summary_list = summary_string.split(',')
117
118    summary = dict(
119        packets_tx=int(summary_list[0].split('packets')[0].strip()),
120        packets_rx=int(summary_list[1].split('packets')[0].strip()),
121        packet_loss=summary_list[2].split('packet')[0].strip(),
122    )
123
124    if 'bytes from' not in results_list[reference_point - 2]:
125        ping_pass = False
126    else:
127        ping_pass = True
128
129    return summary, ping_pass
130
131
132def get_rtt(results_list, packet_loss, location):
133    rtt = dict(min=None, avg=None, max=None)
134
135    if packet_loss != '100.00%':
136        rtt_string = results_list[location]
137        base = rtt_string.split('=')[1]
138        rtt_list = base.split('/')
139
140        rtt['min'] = float(rtt_list[0].lstrip())
141        rtt['avg'] = float(rtt_list[1])
142        rtt['max'] = float(rtt_list[2][:-3])
143
144    return rtt
145
146
147def get_statistics_summary_line(response_as_list):
148    for each in response_as_list:
149        if '---' in each:
150            index = response_as_list.index(each)
151    return index
152
153
154def get_ping_results(command, module):
155    cmd = {'command': command, 'output': 'text'}
156    ping = run_commands(module, [cmd])[0]
157
158    if not ping:
159        module.fail_json(msg="An unexpected error occurred. Check all params.",
160                         command=command, destination=module.params['dest'],
161                         vrf=module.params['vrf'],
162                         source=module.params['source'])
163
164    elif "can't bind to address" in ping:
165        module.fail_json(msg="Can't bind to source address.", command=command)
166    elif "bad context" in ping:
167        module.fail_json(msg="Wrong VRF name inserted.", command=command,
168                         vrf=module.params['vrf'])
169    else:
170        splitted_ping = ping.split('\n')
171        reference_point = get_statistics_summary_line(splitted_ping)
172        summary, ping_pass = get_summary(splitted_ping, reference_point)
173        rtt = get_rtt(splitted_ping, summary['packet_loss'], reference_point + 2)
174
175    return (summary, rtt, ping_pass)
176
177
178def main():
179    argument_spec = dict(
180        dest=dict(required=True),
181        count=dict(required=False, default=5, type='int'),
182        vrf=dict(required=False),
183        source=dict(required=False),
184        state=dict(required=False, choices=['present', 'absent'], default='present'),
185    )
186
187    argument_spec.update(nxos_argument_spec)
188
189    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
190
191    warnings = list()
192    check_args(module, warnings)
193
194    destination = module.params['dest']
195    count = module.params['count']
196    state = module.params['state']
197
198    ping_command = 'ping {0}'.format(destination)
199    for command in ['count', 'source', 'vrf']:
200        arg = module.params[command]
201        if arg:
202            ping_command += ' {0} {1}'.format(command, arg)
203
204    summary, rtt, ping_pass = get_ping_results(ping_command, module)
205
206    results = summary
207    results['rtt'] = rtt
208    results['commands'] = [ping_command]
209
210    if ping_pass and state == 'absent':
211        module.fail_json(msg="Ping succeeded unexpectedly")
212    elif not ping_pass and state == 'present':
213        module.fail_json(msg="Ping failed unexpectedly")
214
215    module.exit_json(**results)
216
217
218if __name__ == '__main__':
219    main()
220