Merge "Adding network resource grab script. Useful during environment setup."
diff --git a/README.rst b/README.rst
index e9a9e15..f35bce7 100644
--- a/README.rst
+++ b/README.rst
@@ -2235,6 +2235,240 @@
                 interface: bond0
                 mac: "ff:ff:ff:ff:ff:ff" (optional)
 
+Check network params on the environment
+---------------------------------------
+
+Grab nics and nics states
+
+.. code-block:: bash
+
+   salt osd001\* net_checks.get_nics
+
+**Example of system output:**
+
+.. code-block:: bash
+
+   osd001.domain.com:
+       |_
+         - bond0
+         - None
+         - 1e:c8:64:42:23:b9
+         - 0
+         - 1500
+       |_
+         - bond1
+         - None
+         - 3c:fd:fe:27:3b:00
+         - 1
+         - 9100
+       |_
+         - fourty1
+         - None
+         - 3c:fd:fe:27:3b:00
+         - 1
+         - 9100
+       |_
+         - fourty2
+         - None
+         - 3c:fd:fe:27:3b:02
+         - 1
+         - 9100
+
+Grab 10G nics PCI addresses for hugepages setup
+
+.. code-block:: bash
+
+   salt cmp001\* net_checks.get_ten_pci
+
+**Example of system output:**
+
+.. code-block:: bash
+
+   cmp001.domain.com:
+       |_
+         - ten1
+         - 0000:19:00.0
+       |_
+         - ten2
+         - 0000:19:00.1
+       |_
+         - ten3
+         - 0000:19:00.2
+       |_
+         - ten4
+         - 0000:19:00.3
+
+Grab ip address for an interface
+
+.. code-block:: bash
+
+   salt cmp001\* net_checks.get_ip iface=one4
+
+**Example of system output:**
+
+.. code-block:: bash
+
+   cmp001.domain.com:
+       10.200.177.101
+
+Grab ip addresses map
+
+.. code-block:: bash
+
+   salt-call net_checks.nodes_addresses
+
+**Example of system output:**
+
+.. code-block:: bash
+
+   local:
+    |_
+      - cid01.domain.com
+      |_
+        |_
+          - pxe
+          - 10.200.177.91
+        |_
+          - control
+          - 10.200.178.91
+    |_
+      - cmn02.domain.com
+      |_
+        |_
+          - storage_access
+          - 10.200.181.67
+        |_
+          - pxe
+          - 10.200.177.67
+        |_
+          - control
+          - 10.200.178.67
+    |_
+      - cmp010.domain.com
+      |_
+        |_
+          - pxe
+          - 10.200.177.110
+        |_
+          - storage_access
+          - 10.200.181.110
+        |_
+          - control
+          - 10.200.178.110
+        |_
+          - vxlan
+          - 10.200.179.110
+
+Verify full mesh connectivity
+
+.. code-block:: bash
+
+   salt-call net_checks.ping_check
+
+**Example of positive system output:**
+
+.. code-block:: bash
+
+   ['PASSED']
+   [INFO    ] ['PASSED']
+   local:
+       True
+
+**Example of system output in case of failure:**
+
+.. code-block:: bash
+
+   FAILED
+   [ERROR   ] FAILED
+   ['control: 10.0.1.92 -> 10.0.1.224: Failed']
+   ['control: 10.0.1.93 -> 10.0.1.224: Failed']
+   ['control: 10.0.1.51 -> 10.0.1.224: Failed']
+   ['control: 10.0.1.102 -> 10.0.1.224: Failed']
+   ['control: 10.0.1.13 -> 10.0.1.224: Failed']
+   ['control: 10.0.1.81 -> 10.0.1.224: Failed']
+   local:
+       False
+
+For this feature to work, please mark addresses with some role.
+Otherwise 'default' role is assumed and mesh would consist of all
+addresses on the environment.
+
+Mesh mark is needed only for interfaces which are enabled and have
+ip address assigned.
+
+Checking dhcp pxe network meaningless, as it is used for salt
+master vs minion communications, therefore treated as checked.
+
+.. code-block:: yaml
+
+   parameters:
+     linux:
+       network:
+         interface:
+           ens3:
+             enabled: true
+             type: eth
+             proto: static
+             address: ${_param:deploy_address}
+             netmask: ${_param:deploy_network_netmask}
+             gateway: ${_param:deploy_network_gateway}
+             mesh: pxe
+
+Check pillars for ip address duplicates
+
+.. code-block:: bash
+
+   salt-call net_checks.verify_addresses
+
+**Example of positive system output:**
+
+.. code-block:: bash
+
+   ['PASSED']
+   [INFO    ] ['PASSED']
+   local:
+       True
+
+**Example of system output in case of failure:**
+
+.. code-block:: bash
+
+   FAILED. Duplicates found
+   [ERROR   ] FAILED. Duplicates found
+   ['gtw01.domain.com', 'gtw02.domain.com', '10.0.1.224']
+   [ERROR   ] ['gtw01.domain.com', 'gtw02.domain.com', '10.0.1.224']
+   local:
+       False
+
+Generate csv report for the env
+
+.. code-block:: bash
+
+   salt -C 'kvm* or cmp* or osd*' net_checks.get_nics_csv \
+     | grep '^\ ' | sed 's/\ *//g' | grep -Ev ^server \
+     | sed '1 i\server,nic_name,ip_addr,mac_addr,link,mtu,chassis_id,chassis_name,port_mac,port_descr'
+
+**Example of system output:**
+
+.. code-block:: bash
+
+   server,nic_name,ip_addr,mac_addr,link,mtu,chassis_id,chassis_name,port_mac,port_descr
+   cmp010.domain.com,bond0,None,b4:96:91:10:5b:3a,1,1500,,,,
+   cmp010.domain.com,bond0.21,10.200.178.110,b4:96:91:10:5b:3a,1,1500,,,,
+   cmp010.domain.com,bond0.22,10.200.179.110,b4:96:91:10:5b:3a,1,1500,,,,
+   cmp010.domain.com,bond1,None,3c:fd:fe:34:ad:22,0,1500,,,,
+   cmp010.domain.com,bond1.24,10.200.181.110,3c:fd:fe:34:ad:22,0,1500,,,,
+   cmp010.domain.com,fourty5,None,3c:fd:fe:34:ad:20,0,9000,,,,
+   cmp010.domain.com,fourty6,None,3c:fd:fe:34:ad:22,0,9000,,,,
+   cmp010.domain.com,one1,None,b4:96:91:10:5b:38,0,1500,,,,
+   cmp010.domain.com,one2,None,b4:96:91:10:5b:39,1,1500,f0:4b:3a:8f:75:40,exnfvaa18-20,548,ge-0/0/22
+   cmp010.domain.com,one3,None,b4:96:91:10:5b:3a,1,1500,f0:4b:3a:8f:75:40,exnfvaa18-20,547,ge-0/0/21
+   cmp010.domain.com,one4,10.200.177.110,b4:96:91:10:5b:3b,1,1500,f0:4b:3a:8f:75:40,exnfvaa18-20,546,ge-0/0/20
+   cmp011.domain.com,bond0,None,b4:96:91:13:6c:aa,1,1500,,,,
+   cmp011.domain.com,bond0.21,10.200.178.111,b4:96:91:13:6c:aa,1,1500,,,,
+   cmp011.domain.com,bond0.22,10.200.179.111,b4:96:91:13:6c:aa,1,1500,,,,
+   ...
+
 Usage
 =====
 
diff --git a/_modules/net_checks.py b/_modules/net_checks.py
new file mode 100644
index 0000000..cb0f4fc
--- /dev/null
+++ b/_modules/net_checks.py
@@ -0,0 +1,279 @@
+from os import listdir, path
+from subprocess import Popen,PIPE
+from re import findall as refindall
+from re import search as research
+import salt.utils
+import socket, struct, fcntl
+import logging
+
+logger = logging.getLogger(__name__)
+stream = logging.StreamHandler()
+logger.addHandler(stream)
+
+def get_ip(iface='ens2'):
+
+    ''' Get ip address from an interface if applicable
+
+    :param iface: Interface name. Type: str
+
+    '''
+
+    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    sockfd = sock.fileno()
+    SIOCGIFADDR = 0x8915
+    ifreq = struct.pack('16sH14s', iface, socket.AF_INET, '\x00'*14)
+
+    try:
+        res = fcntl.ioctl(sockfd, SIOCGIFADDR, ifreq)
+    except:
+        logger.debug("No ip addresses assigned to %s" % iface)
+        return None
+
+    ip = struct.unpack('16sH2x4s8x', res)[2]
+    return socket.inet_ntoa(ip)
+
+def get_nics():
+
+    ''' List nics '''
+
+    nics = []
+    nics_list = listdir('/sys/class/net/')
+    for nic_name in nics_list:
+        if research('(br|bond|ens|enp|eth|one|ten|fourty)[0-9]+', nic_name):
+
+            # Interface should be in "up" state in order to get carrier status
+            Popen("ip li set dev " + nic_name + " up", shell=True, stdout=PIPE)
+
+            with open("/sys/class/net/" + nic_name + "/carrier", 'r') as f:
+                try:
+                    carrier = int(f.read())
+                except:
+                    carrier = 0
+
+            bond = ""
+            if path.isfile("/sys/class/net/" + nic_name + "/master/uevent"):
+                with open("/sys/class/net/" + nic_name + "/master/uevent", 'r') as f:
+                    for line in f:
+                        sline = line.strip()
+                        if 'INTERFACE=bond' in sline:
+                            bond = sline.split('=')[1]
+            if len(bond) == 0:
+                with open("/sys/class/net/" + nic_name + "/address", 'r') as f:
+                    macaddr = f.read().strip()
+            else:
+                with open("/proc/net/bonding/" + bond, 'r') as f:
+                    line = f.readline()
+                    if_struct = False
+                    while line:
+                        sline = line.strip()
+                        if 'Slave Interface: ' + nic_name in sline and not if_struct:
+                            if_struct = True
+                        if 'Permanent HW addr: ' in sline and if_struct:
+                            macaddr = sline.split()[3]
+                            break
+                        line = f.readline()
+
+            with open("/sys/class/net/" + nic_name + "/mtu", 'r') as f:
+                mtu = f.read()
+
+            ip = str(get_ip(nic_name))
+
+            nics.append([nic_name, ip, macaddr, carrier, mtu])
+
+    return sorted(nics)
+
+def get_ten_pci():
+
+    ''' List ten nics pci addresses '''
+
+    nics = []
+    nics_list = listdir('/sys/class/net/')
+    for nic_name in nics_list:
+        if research('ten[0-9]+', nic_name):
+            with open("/sys/class/net/" + nic_name + "/device/uevent", 'r') as f:
+                for line in f:
+                    sline = line.strip()
+                    if "PCI_SLOT_NAME=" in sline:
+                        nics.append([nic_name , sline.split("=")[1]])
+
+    return sorted(nics)
+
+def mesh_ping(mesh):
+
+    ''' One to many ICMP check
+
+    :param hosts: Target hosts. Type: list of ip addresses
+
+    '''
+
+    io = []
+    minion_id = __salt__['config.get']('id')
+
+    for host, hostobj in mesh:
+        if host == minion_id:
+            for mesh_net, addr, targets in hostobj:
+                if addr in targets:
+                    targets.remove(addr)
+                for tgt in targets:
+                    # This one will run in parallel with everyone else
+                    worker = Popen("ping -c 1 -w 1 -W 1 " + str(tgt), \
+                        shell=True, stdout=PIPE, stderr=PIPE)
+                    ping_out = worker.communicate()[0]
+                    if worker.returncode != 0:
+                        io.append(mesh_net + ': ' + addr + ' -> ' + tgt + ': Failed')
+
+    return io
+
+def minion_list():
+
+    ''' List registered minions '''
+
+    return listdir('/etc/salt/pki/master/minions/')
+
+def verify_addresses():
+
+    ''' Verify addresses taken from pillars '''
+
+    nodes = nodes_addresses()
+    verifier = {}
+    failed = []
+
+    for node, nodeobj in nodes:
+        for item in nodeobj:
+            addr = item[1]
+            if addr in verifier:
+                failed.append([node,verifier[addr],addr])
+            else:
+                verifier[addr] = node
+
+    if failed:
+        logger.error("FAILED. Duplicates found")
+        logger.error(failed)
+        return False
+    else:
+        logger.setLevel(logging.INFO)
+        logger.info(["PASSED"])
+        return True
+
+def nodes_addresses():
+
+    ''' List servers addresses '''
+
+    compound = 'linux:network:interface'
+    out = __salt__['saltutil.cmd']( tgt='I@' + compound,
+                                    tgt_type='compound',
+                                    fun='pillar.get',
+                                    arg=[compound],
+                                    timeout=10
+                                  ) or None
+
+    servers = []
+    for minion in minion_list():
+        addresses = []
+        if minion in out:
+            ifaces = out[minion]['ret']
+            for iface in ifaces:
+                ifobj = ifaces[iface]
+                if ifobj['enabled'] and 'address' in ifobj:
+                    if 'mesh' in ifobj:
+                        mesh = ifobj['mesh']
+                    else:
+                        mesh = 'default'
+                    addresses.append([mesh, ifobj['address']])
+            servers.append([minion,addresses])
+
+    return servers
+
+def get_mesh():
+
+    ''' Build addresses mesh '''
+
+    full_mesh = {}
+    nodes = nodes_addresses()
+
+    for node, nodeobj in nodes:
+        for item in nodeobj:
+            mesh = item[0]
+            addr = item[1]
+            if not mesh in full_mesh:
+                full_mesh[mesh] = []
+            full_mesh[mesh].append(addr)
+
+    for node, nodeobj in nodes:
+        for item in nodeobj:
+            mesh = item[0]
+            tgts = full_mesh[mesh]
+            item.append(tgts)
+
+    return nodes
+
+def ping_check():
+
+    ''' Ping addresses in a mesh '''
+
+    mesh = get_mesh()
+    out = __salt__['saltutil.cmd']( tgt='*',
+                                    tgt_type='glob',
+                                    fun='net_checks.mesh_ping',
+                                    arg=[mesh],
+                                    timeout=10
+                                  ) or None
+
+    failed = []
+
+    if out:
+        for minion in out:
+            ret = out[minion]['ret']
+            if ret:
+                failed.append(ret)
+    else:
+        failed = ["No response from minions"]
+
+    if failed:
+        logger.error("FAILED")
+        logger.error('\n'.join(str(x) for x in failed))
+        return False
+    else:
+        logger.setLevel(logging.INFO)
+        logger.info(["PASSED"])
+        return True
+
+def get_nics_csv(delim=","):
+
+    ''' List nics in csv format
+
+    :param delim: Delimiter char. Type: str
+
+    '''
+
+    header = "server,nic_name,ip_addr,mac_addr,link,chassis_id,chassis_name,port_mac,port_descr\n"
+    io = ""
+
+    # Try to reuse lldp output if possible
+    try:
+        lldp_info = Popen("lldpcli -f keyvalue s n s", shell=True, stdout=PIPE).communicate()[0]
+    except:
+        lldp_info = ""
+
+    for nic in get_nics():
+        lldp = ""
+        nic_name = nic[0]
+        if research('(one|ten|fourty)[0-9]+', nic_name):
+            # Check if we can fetch lldp data for that nic
+            for line in lldp_info.splitlines():
+                chassis = 'lldp.' + nic[0] + '.chassis'
+                port = 'lldp.' + nic[0] + '.port'
+                if chassis in line or port in line:
+                    lldp += delim + line.split('=')[1]
+        if not lldp:
+            lldp = delim + delim + delim + delim
+
+        io += __salt__['config.get']('id') + \
+              delim + nic_name + \
+              delim + str(nic[1]).strip() + \
+              delim + str(nic[2]).strip() + \
+              delim + str(nic[3]).strip() + \
+              delim + str(nic[4]).strip() + \
+              lldp + "\n"
+
+    return header + io