#!/usr/bin/env python

# Copyright (c) 1999-2016, Juniper Networks Inc.
#               2016, Damien Garros
#
# All rights reserved.
#
# License: Apache 2.0
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
#
# * Neither the name of the Juniper Networks nor the
#   names of its contributors may be used to endorse or promote products
#   derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

DOCUMENTATION = '''
---
module: junos_ping
author: Damien Garros, Juniper Networks
version_added: "1.3.1"
short_description: execute ping on junos devices
description:
    - execute ping on junos devices
requirements:
    - junos-eznc >= 1.2.2
options:
  host:
    description:
        - Set to {{ inventory_hostname }}
    required: true
  user:
    description:
        - Login username
    required: false
    default: $USER
  passwd:
    description:
        - Login password
    required: false
    default: assumes ssh-key active
  port:
    description:
        - port number to use when connecting to the device
    required: false
    default: 830
  ssh_private_key_file:
    description:
        - This can be used if you need to provide a private key rather than
          loading the key into the ssh-key-ring/environment.  if your
          ssh-key requires a password, then you must provide it via
          **passwd**
    required: false
    default: None
  mode:
    description:
        - mode of console connection (telnet/serial). If mode is not
          provided SSH connection is used.
    required: false
    default: None
  dest_ip:
    description:
        - Destination ip
    required: true
  source_ip:
    description:
        - Source IP used to send the ping
    required: false
  rapid:
    description:
        - Execute ping at 100pps instead of 1pps
    required: false
    default: true
  ttl:
    description:
        - Maximum number of IP routers (IP hops) allowed between source and destination
    required: false
  routing_instance:
    description:
        - Name of the routing instance to use to send the ping
    required: false
  timeout:
    description:
        - Extend the NETCONF RPC timeout beyond the default value of
          30 seconds. Set this value to accommodate pings
          that might take longer than the default timeout interval.
    required: false
    default: "0"
  interface:
    description:
        - Interface used to send traffic out
    required: false
  acceptable_packet_loss:
    description:
        - Number of packets lost that is acceptable to consider the test PASS
    required: false
    default: "0"
  count:
    description:
        - Number of packet to send
    required: false
    default: 5
  size:
    description:
        - The size of the ICMP payload of the ping.
    required: false
    default: None (default size)
  do_not_fragment:
    description:
        - Set Don't Fragment bit
    required: false
    default: false
'''

EXAMPLES = '''
# Simple example
  tasks:
    - name: "Execute ping peer"
      junos_ping:
        host={{ junos_host }}
        port={{ netconf_port }}
        user={{ ansible_ssh_user }}
        passwd={{ ansible_ssh_pass }}
        dest_ip=8.8.8.8

# ping over console server connection using PyEZ >= 2.0
  tasks:
    - name: "Execute ping peer"
      junos_ping:
        host={{ inventory_hostname }}
        port=2011
        mode='telnet'
        user={{ ansible_ssh_user }}
        passwd={{ ansible_ssh_pass }}
        dest_ip=8.8.8.8

# Using loop and more parameters
  tasks:
    - name: "Execute ping peer"
      junos_ping:
        host={{ junos_host }}
        port={{ netconf_port }}
        user={{ ansible_ssh_user }}
        passwd={{ ansible_ssh_pass }}
        dest_ip={{ item.peer_ip }}
        source_ip={{ item.local_ip }}
        do_not_fragment=True
        ttl=1
      with_items: "{{underlay.neighbors}}"
'''

import os
import sys
import re
from lxml import etree
from distutils.version import LooseVersion

def main():
    module = AnsibleModule(
        argument_spec=dict(
            host=dict(required=True),
            user=dict(required=False, default=os.getenv('USER')),
            passwd=dict(required=False, default=None, no_log=True),
            port=dict(required=False, default=830),
            ssh_private_key_file=dict(required=False, default=None),
            mode=dict(required=False, default=None),
            timeout=dict(required=False, default=0),
            dest_ip=dict(required=True, default=None),
            source_ip=dict(required=False, default=None),
            interface=dict(required=False, default=None),
            rapid=dict(required=False, type='bool', default=True),
            routing_instance=dict(required=False, default=None),
            ttl=dict(required=False, default=None),
            acceptable_packet_loss=dict(required=False, default=0),
            count=dict(required=False, default='5'),
            size=(dict(required=False, default=None)),
            do_not_fragment=dict(required=False, type='bool', default=False)
            ),
        supports_check_mode=False
    )

    results = {}
    m_args = module.params

    try:
        from jnpr.junos import Device
        from jnpr.junos.version import VERSION
        if not LooseVersion(VERSION) >= LooseVersion('1.2.2'):
            module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module')
    except ImportError as ex:
        module.fail_json(msg='ImportError: %s' % ex.message)

    if m_args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'):
        module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection')

    # Open connection to device
    dev = Device(
            m_args['host'],
            user=m_args['user'],
            passwd=m_args['passwd'],
            port=m_args['port'],
            ssh_private_key_file=m_args['ssh_private_key_file'],
            mode=m_args['mode'],
            gather_facts=False)

    try:
        dev.open()
    except Exception as err:
        msg = 'Unable to connect to {0}: {1}'.format(m_args['host'], str(err))
        module.fail_json(msg=msg)
        return

    results['dest_ip'] = m_args['dest_ip']
    results['count'] = m_args['count']
    results['rapid'] = m_args['rapid']
    results['do_not_fragment'] = m_args['do_not_fragment']

    results['changed'] = False

    # Prepare parameters
    ping_params = dict(
            host=m_args['dest_ip'],
            count=str(m_args['count']))

    if  m_args['rapid'] is True:
        ping_params['rapid'] = m_args['rapid']

    if m_args['do_not_fragment'] is True:
        ping_params['do_not_fragment'] = m_args['do_not_fragment']

    if m_args['source_ip'] is not None:
        ping_params['source'] = m_args['source_ip']
        results['source_ip'] = m_args['source_ip']

    if m_args['routing_instance'] is not None:
        ping_params['routing_instance'] = m_args['routing_instance']
        results['routing_instance'] = m_args['routing_instance']

    if m_args['interface'] is not None:
        ping_params['interface'] = m_args['interface']
        results['interface'] = m_args['interface']

    if m_args['ttl'] is not None:
        ping_params['ttl'] = str(m_args['ttl'])
        results['ttl'] = str(m_args['ttl'])

    if m_args['size'] is not None:
        ping_params['size'] = str(m_args['size'])
        results['size'] = str(m_args['size'])

    ## Change default Timeout
    timeout = int(m_args['timeout'])
    if timeout > 0:
        dev.timeout = timeout

    try:

        # Execute Ping
        rpc_reply = dev.rpc.ping(**ping_params)

    except Exception as err:
        results['failed'] = True
        results['msg'] = "unable to execute ping due to:{0}".format(err.message)
        dev.close()
        raise err

    try:
        # Try to Find probe summary
        probe_summary = rpc_reply.find('probe-results-summary')
        if probe_summary is None:
            results['failed'] = True
            results['msg'] = rpc_reply.findtext('rpc-error/error-message').strip()
            dev.close()
            module.exit_json(**results)

        # Extract packet loss
        packet_loss = probe_summary.findtext("packet-loss")
        packets_sent = probe_summary.findtext("probes-sent")
        responses_received = probe_summary.findtext("responses-received")

        if packet_loss is None:
            results['msg'] = 'Unable to collect results from ping, response was not found or was empty'
            results['failed'] = True
            dev.close()
            module.exit_json(**results)

        packet_loss = packet_loss.strip()
        packets_sent = packets_sent.strip()
        responses_received = responses_received.strip()
        results['msg'] = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss), str(packets_sent), str(responses_received))
        results['packet_loss'] = packet_loss
        results['packets_sent'] = packets_sent
        results['packets_received'] = responses_received

        if int(packet_loss) > int(m_args['acceptable_packet_loss']):
            results['failed'] = True
            dev.close()
            module.exit_json(**results)

    except Exception as err:
        results['failed'] = True
        results['msg'] = "unable to analyze ping results  due to:{0}".format(err.message)
        dev.close()
        raise err

    dev.close()
    module.exit_json(**results)

from ansible.module_utils.basic import *

if __name__ == '__main__':
    main()
