Useful during environment setup. Change-Id: Ic8d4d82cc6f6afb0ac2a6e4285c3b681bb895b6dpull/170/head
interface: bond0 | interface: bond0 | ||||
mac: "ff:ff:ff:ff:ff:ff" (optional) | 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 | Usage | ||||
===== | ===== | ||||
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 |