Useful during environment setup. Change-Id: Ic8d4d82cc6f6afb0ac2a6e4285c3b681bb895b6dpull/170/head
@@ -2235,6 +2235,240 @@ and on-boot after an interface initialization. | |||
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 | |||
===== | |||
@@ -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 |