| import fcntl |
| import logging |
| import socket |
| import struct |
| from os import listdir, path |
| from re import search as research |
| from subprocess import PIPE, Popen |
| |
| 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 mesh not 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 |