blob: 982ee895b8bb08331db31e9a28070df6658651e7 [file] [log] [blame]
Alexe0c5b9e2019-04-23 18:51:23 -05001import fcntl
2import logging
3import socket
4import struct
5from os import listdir, path
6from re import search as research
7from subprocess import PIPE, Popen
8
9logger = logging.getLogger(__name__)
10stream = logging.StreamHandler()
11logger.addHandler(stream)
12
13
14def get_ip(iface='ens2'):
15
16 ''' Get ip address from an interface if applicable
17
18 :param iface: Interface name. Type: str
19
20 '''
21
22 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
23 sockfd = sock.fileno()
24 SIOCGIFADDR = 0x8915
25 ifreq = struct.pack('16sH14s', iface, socket.AF_INET, '\x00'*14)
26
27 try:
28 res = fcntl.ioctl(sockfd, SIOCGIFADDR, ifreq)
29 except:
30 logger.debug("No ip addresses assigned to %s" % iface)
31 return None
32
33 ip = struct.unpack('16sH2x4s8x', res)[2]
34 return socket.inet_ntoa(ip)
35
36
37def get_nics():
38
39 ''' List nics '''
40
41 nics = []
42 nics_list = listdir('/sys/class/net/')
43 for nic_name in nics_list:
44 if research('(br|bond|ens|enp|eth|one|ten|fourty)[0-9]+', nic_name):
45
46 # Interface should be in "up" state in order to get carrier status
47 Popen("ip li set dev " + nic_name + " up", shell=True, stdout=PIPE)
48
49 with open("/sys/class/net/" + nic_name + "/carrier", 'r') as f:
50 try:
51 carrier = int(f.read())
52 except:
53 carrier = 0
54
55 bond = ""
56 if path.isfile("/sys/class/net/" + nic_name + "/master/uevent"):
57 with open(
58 "/sys/class/net/" + nic_name + "/master/uevent", 'r'
59 ) as f:
60 for line in f:
61 sline = line.strip()
62 if 'INTERFACE=bond' in sline:
63 bond = sline.split('=')[1]
64 if len(bond) == 0:
65 with open("/sys/class/net/" + nic_name + "/address", 'r') as f:
66 macaddr = f.read().strip()
67 else:
68 with open("/proc/net/bonding/" + bond, 'r') as f:
69 line = f.readline()
70 if_struct = False
71 while line:
72 sline = line.strip()
73 if 'Slave Interface: ' + nic_name in sline and not if_struct:
74 if_struct = True
75 if 'Permanent HW addr: ' in sline and if_struct:
76 macaddr = sline.split()[3]
77 break
78 line = f.readline()
79
80 with open("/sys/class/net/" + nic_name + "/mtu", 'r') as f:
81 mtu = f.read()
82
83 ip = str(get_ip(nic_name))
84
85 nics.append([nic_name, ip, macaddr, carrier, mtu])
86
87 return sorted(nics)
88
89
90def get_ten_pci():
91
92 ''' List ten nics pci addresses '''
93
94 nics = []
95 nics_list = listdir('/sys/class/net/')
96 for nic_name in nics_list:
97 if research('ten[0-9]+', nic_name):
98 with open(
99 "/sys/class/net/" + nic_name + "/device/uevent", 'r'
100 ) as f:
101 for line in f:
102 sline = line.strip()
103 if "PCI_SLOT_NAME=" in sline:
104 nics.append([nic_name, sline.split("=")[1]])
105
106 return sorted(nics)
107
108
109def mesh_ping(mesh):
110
111 ''' One to many ICMP check
112
113 :param hosts: Target hosts. Type: list of ip addresses
114
115 '''
116
117 io = []
118 minion_id = __salt__['config.get']('id')
119
120 for host, hostobj in mesh:
121 if host == minion_id:
122 for mesh_net, addr, targets in hostobj:
123 if addr in targets:
124 targets.remove(addr)
125 for tgt in targets:
126 # This one will run in parallel with everyone else
127 worker = Popen(
128 "ping -c 1 -w 1 -W 1 " + str(tgt),
129 shell=True, stdout=PIPE, stderr=PIPE
130 )
131 ping_out = worker.communicate()[0]
132 if worker.returncode != 0:
133 io.append(
134 mesh_net + ': ' + addr + ' -> ' + tgt + ': Failed'
135 )
136
137 return io
138
139
140def minion_list():
141
142 ''' List registered minions '''
143
144 return listdir('/etc/salt/pki/master/minions/')
145
146
147def verify_addresses():
148
149 ''' Verify addresses taken from pillars '''
150
151 nodes = nodes_addresses()
152 verifier = {}
153 failed = []
154
155 for node, nodeobj in nodes:
156 for item in nodeobj:
157 addr = item[1]
158 if addr in verifier:
159 failed.append([node, verifier[addr], addr])
160 else:
161 verifier[addr] = node
162
163 if failed:
164 logger.error("FAILED. Duplicates found")
165 logger.error(failed)
166 return False
167 else:
168 logger.setLevel(logging.INFO)
169 logger.info(["PASSED"])
170 return True
171
172
173def nodes_addresses():
174
175 ''' List servers addresses '''
176
177 compound = 'linux:network:interface'
178 out = __salt__['saltutil.cmd'](
179 tgt='I@' + compound,
180 tgt_type='compound',
181 fun='pillar.get',
182 arg=[compound],
183 timeout=10
184 ) or None
185
186 servers = []
187 for minion in minion_list():
188 addresses = []
189 if minion in out:
190 ifaces = out[minion]['ret']
191 for iface in ifaces:
192 ifobj = ifaces[iface]
193 if ifobj['enabled'] and 'address' in ifobj:
194 if 'mesh' in ifobj:
195 mesh = ifobj['mesh']
196 else:
197 mesh = 'default'
198 addresses.append([mesh, ifobj['address']])
199 servers.append([minion, addresses])
200
201 return servers
202
203
204def get_mesh():
205
206 ''' Build addresses mesh '''
207
208 full_mesh = {}
209 nodes = nodes_addresses()
210
211 for node, nodeobj in nodes:
212 for item in nodeobj:
213 mesh = item[0]
214 addr = item[1]
215 if mesh not in full_mesh:
216 full_mesh[mesh] = []
217 full_mesh[mesh].append(addr)
218
219 for node, nodeobj in nodes:
220 for item in nodeobj:
221 mesh = item[0]
222 tgts = full_mesh[mesh]
223 item.append(tgts)
224
225 return nodes
226
227
228def ping_check():
229
230 ''' Ping addresses in a mesh '''
231
232 mesh = get_mesh()
233 out = __salt__['saltutil.cmd'](
234 tgt='*',
235 tgt_type='glob',
236 fun='net_checks.mesh_ping',
237 arg=[mesh],
238 timeout=10
239 ) or None
240
241 failed = []
242
243 if out:
244 for minion in out:
245 ret = out[minion]['ret']
246 if ret:
247 failed.append(ret)
248 else:
249 failed = ["No response from minions"]
250
251 if failed:
252 logger.error("FAILED")
253 logger.error('\n'.join(str(x) for x in failed))
254 return False
255 else:
256 logger.setLevel(logging.INFO)
257 logger.info(["PASSED"])
258 return True
259
260
261def get_nics_csv(delim=","):
262
263 ''' List nics in csv format
264
265 :param delim: Delimiter char. Type: str
266
267 '''
268
269 header = "server,nic_name,ip_addr,mac_addr,link,chassis_id,chassis_name,port_mac,port_descr\n"
270 io = ""
271
272 # Try to reuse lldp output if possible
273 try:
274 lldp_info = Popen(
275 "lldpcli -f keyvalue s n s",
276 shell=True,
277 stdout=PIPE
278 ).communicate()[0]
279 except:
280 lldp_info = ""
281
282 for nic in get_nics():
283 lldp = ""
284 nic_name = nic[0]
285 if research('(one|ten|fourty)[0-9]+', nic_name):
286 # Check if we can fetch lldp data for that nic
287 for line in lldp_info.splitlines():
288 chassis = 'lldp.' + nic[0] + '.chassis'
289 port = 'lldp.' + nic[0] + '.port'
290 if chassis in line or port in line:
291 lldp += delim + line.split('=')[1]
292 if not lldp:
293 lldp = delim + delim + delim + delim
294
295 io += __salt__['config.get']('id') + \
296 delim + nic_name + \
297 delim + str(nic[1]).strip() + \
298 delim + str(nic[2]).strip() + \
299 delim + str(nic[3]).strip() + \
300 delim + str(nic[4]).strip() + \
301 lldp + "\n"
302
303 return header + io