Fixes various bugs and adds some improvements
[apex.git] / lib / common-functions.sh
1 #!/usr/bin/env bash
2 # Common Functions used by  OPNFV Apex
3 # author: Tim Rozet (trozet@redhat.com)
4
5 ##converts subnet mask to prefix
6 ##params: subnet mask
7 function prefix2mask {
8   # Number of args to shift, 255..255, first non-255 byte, zeroes
9    set -- $(( 5 - ($1 / 8) )) 255 255 255 255 $(( (255 << (8 - ($1 % 8))) & 255 )) 0 0 0
10    [ $1 -gt 1 ] && shift $1 || shift
11    echo ${1-0}.${2-0}.${3-0}.${4-0}
12 }
13
14 ##find ip of interface
15 ##params: interface name
16 function find_ip {
17   ip addr show $1 | grep -Eo '^\s+inet\s+[\.0-9]+' | awk '{print $2}'
18 }
19
20 ##finds subnet of ip and netmask
21 ##params: ip, netmask
22 function find_subnet {
23   IFS=. read -r i1 i2 i3 i4 <<< "$1"
24   IFS=. read -r m1 m2 m3 m4 <<< "$2"
25   printf "%d.%d.%d.%d\n" "$((i1 & m1))" "$((i2 & m2))" "$((i3 & m3))" "$((i4 & m4))"
26 }
27
28 ##verify subnet has at least n IPs
29 ##params: subnet mask, n IPs
30 function verify_subnet_size {
31   IFS=. read -r i1 i2 i3 i4 <<< "$1"
32   num_ips_required=$2
33
34   ##this function assumes you would never need more than 254
35   ##we check here to make sure
36   if [ "$num_ips_required" -ge 254 ]; then
37     echo -e "\n\n${red}ERROR: allocating more than 254 IPs is unsupported...Exiting${reset}\n\n"
38     return 1
39   fi
40
41   ##we just return if 3rd octet is not 255
42   ##because we know the subnet is big enough
43   if [ "$i3" -ne 255 ]; then
44     return 0
45   elif [ $((254-$i4)) -ge "$num_ips_required" ]; then
46     return 0
47   else
48     echo -e "\n\n${red}ERROR: Subnet is too small${reset}\n\n"
49     return 1
50   fi
51 }
52
53 ##finds last usable ip (broadcast minus 1) of a subnet from an IP and netmask
54 ## Warning: This function only works for IPv4 at the moment.
55 ##params: ip, netmask
56 function find_last_ip_subnet {
57   IFS=. read -r i1 i2 i3 i4 <<< "$1"
58   IFS=. read -r m1 m2 m3 m4 <<< "$2"
59   IFS=. read -r s1 s2 s3 s4 <<< "$((i1 & m1)).$((i2 & m2)).$((i3 & m3)).$((i4 & m4))"
60   printf "%d.%d.%d.%d\n" "$((255 - $m1 + $s1))" "$((255 - $m2 + $s2))" "$((255 - $m3 + $s3))" "$((255 - $m4 + $s4 - 1))"
61 }
62
63 ##increments subnet by a value
64 ##params: ip, value
65 ##assumes low value
66 function increment_subnet {
67   IFS=. read -r i1 i2 i3 i4 <<< "$1"
68   printf "%d.%d.%d.%d\n" "$i1" "$i2" "$i3" "$((i4 | $2))"
69 }
70
71 ##finds netmask of interface
72 ##params: interface
73 ##returns long format 255.255.x.x
74 function find_netmask {
75   ifconfig $1 | grep -Eo 'netmask\s+[\.0-9]+' | awk '{print $2}'
76 }
77
78 ##finds short netmask of interface
79 ##params: interface
80 ##returns short format, ex: /21
81 function find_short_netmask {
82   echo "/$(ip addr show $1 | grep -Eo '^\s+inet\s+[\/\.0-9]+' | awk '{print $2}' | cut -d / -f2)"
83 }
84
85 ##increments next IP
86 ##params: ip
87 ##assumes a /24 subnet
88 function next_ip {
89   baseaddr="$(echo $1 | cut -d. -f1-3)"
90   lsv="$(echo $1 | cut -d. -f4)"
91   if [ "$lsv" -ge 254 ]; then
92     return 1
93   fi
94   ((lsv++))
95   echo $baseaddr.$lsv
96 }
97
98 ##subtracts a value from an IP address
99 ##params: last ip, ip_count
100 ##assumes ip_count is less than the last octect of the address
101 subtract_ip() {
102   IFS=. read -r i1 i2 i3 i4 <<< "$1"
103   ip_count=$2
104   if [ $i4 -lt $ip_count ]; then
105     echo -e "\n\n${red}ERROR: Can't subtract $ip_count from IP address $1  Exiting${reset}\n\n"
106     exit 1
107   fi
108   printf "%d.%d.%d.%d\n" "$i1" "$i2" "$i3" "$((i4 - $ip_count ))"
109 }
110
111 ##check if IP is in use
112 ##params: ip
113 ##ping ip to get arp entry, then check arp
114 function is_ip_used {
115   ping -c 5 $1 > /dev/null 2>&1
116   arp -n | grep "$1 " | grep -iv incomplete > /dev/null 2>&1
117 }
118
119 ##find next usable IP
120 ##params: ip
121 function next_usable_ip {
122   new_ip=$(next_ip $1)
123   while [ "$new_ip" ]; do
124     if ! is_ip_used $new_ip; then
125       echo $new_ip
126       return 0
127     fi
128     new_ip=$(next_ip $new_ip)
129   done
130   return 1
131 }
132
133 ##increment ip by value
134 ##params: ip, amount to increment by
135 ##increment_ip $next_private_ip 10
136 function increment_ip {
137   baseaddr="$(echo $1 | cut -d. -f1-3)"
138   lsv="$(echo $1 | cut -d. -f4)"
139   incrval=$2
140   lsv=$((lsv+incrval))
141   if [ "$lsv" -ge 254 ]; then
142     return 1
143   fi
144   echo $baseaddr.$lsv
145 }
146
147 ##finds gateway on system
148 ##params: interface to validate gateway on (optional)
149 ##find_gateway em1
150 function find_gateway {
151   local gw gw_interface
152   if [ -z "$1"  ]; then
153     return 1
154   fi
155   gw=$(ip route | grep default | awk '{print $3}')
156   gw_interface=$(ip route get $gw | awk '{print $3}')
157   if [ -n "$1" ]; then
158     if [ "$gw_interface" == "$1" ]; then
159       echo ${gw}
160     fi
161   fi
162 }
163
164 ##finds subnet in CIDR notation for interface
165 ##params: interface to find CIDR
166 function find_cidr {
167   local cidr network ip netmask short_mask
168   if [ -z "$1"  ]; then
169     return 1
170   fi
171   ip=$(find_ip $1)
172   netmask=$(find_netmask $1)
173   if [[ -z "$ip" || -z "$netmask" ]]; then
174     return 1
175   fi
176   network=$(find_subnet ${ip} ${netamsk})
177   short_mask=$(find_short_netmask $1)
178   if [[ -z "$network" || -z "$short_mask" ]]; then
179     return 1
180   fi
181   cidr="${subnet}'\'${short_mask}"
182   echo ${cidr}
183 }
184
185 ##finds block of usable IP addresses for an interface
186 ##simply returns at the moment the correct format
187 ##after first 20 IPs, and leave 20 IPs at end of subnet (for floating ips, etc)
188 ##params: interface to find IP
189 function find_usable_ip_range {
190   local interface_ip subnet_mask first_block_ip last_block_ip
191   if [ -z "$1"  ]; then
192     return 1
193   fi
194   interface_ip=$(find_ip $1)
195   subnet_mask=$(find_netmask $1)
196   if [[ -z "$interface_ip" || -z "$subnet_mask" ]]; then
197     return 1
198   fi
199   interface_ip=$(increment_ip ${interface_ip} 20)
200   first_block_ip=$(next_usable_ip ${interface_ip})
201   if [ -z "$first_block_ip" ]; then
202     return 1
203   fi
204   last_block_ip=$(find_last_ip_subnet ${interface_ip} ${subnet_mask})
205   if [ -z "$last_block_ip" ]; then
206     return 1
207   else
208     last_block_ip=$(subtract_ip ${last_block_ip} 21)
209     echo "${first_block_ip},${last_block_ip}"
210   fi
211
212 }
213
214 ##generates usable IP range in correct format based on CIDR
215 ##assumes the first 20 IPs are used (by instack or otherwise)
216 ##params: cidr
217 function generate_usable_ip_range {
218   local first_ip first_block_ip last_block_ip
219   #first_ip=$(ipcalc  -nb $1 | grep HostMin: | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
220   first_ip=$(ipcalc -nmpb $1 | grep NETWORK= | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
221   first_ip=$(increment_ip ${first_ip} 1)
222   first_block_ip=$(increment_ip ${first_ip} 20)
223   #last_block_ip=$(ipcalc  -nb $1 | grep HostMax: | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
224   last_block_ip=$(ipcalc -nmpb $1 | grep BROADCAST= | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
225   last_block_ip=$(subtract_ip ${last_block_ip} 1)
226   if [[ -z "$first_block_ip" || -z "$last_block_ip" ]]; then
227     return 1
228   else
229     last_block_ip=$(subtract_ip ${last_block_ip} 21)
230     echo "${first_block_ip},${last_block_ip}"
231   fi
232 }
233
234 ##find the instack IP address
235 ##finds first usable IP on subnet
236 ##params: interface
237 function find_provisioner_ip {
238   local interface_ip
239   if [ -z "$1"  ]; then
240     return 1
241   fi
242   interface_ip=$(find_ip $1)
243   if [ -z "$interface_ip" ]; then
244     return 1
245   fi
246   echo $(increment_ip ${interface_ip} 1)
247 }
248
249 ##generates instack IP address based on CIDR
250 ##params: cidr
251 function generate_provisioner_ip {
252   local provisioner_ip
253   #provisioner_ip=$(ipcalc  -nb $1 | grep HostMin: | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
254   provisioner_ip=$(ipcalc -nmpb $1 | grep NETWORK= | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
255   if [ -z "$provisioner_ip" ]; then
256     return 1
257   fi
258   provisioner_ip=$(increment_ip ${provisioner_ip} 1)
259   echo "$provisioner_ip"
260 }
261
262 ##finds the dhcp range available via interface
263 ##uses first 8 IPs, after 2nd IP
264 ##params: interface
265 function find_dhcp_range {
266   local dhcp_range_start dhcp_range_end interface_ip
267   if [ -z "$1"  ]; then
268     return 1
269   fi
270   interface_ip=$(find_ip $1)
271   if [ -z "$interface_ip" ]; then
272     return 1
273   fi
274   dhcp_range_start=$(increment_ip ${interface_ip} 2)
275   dhcp_range_end=$(increment_ip ${dhcp_range_start} 8)
276   echo "${dhcp_range_start},${dhcp_range_end}"
277 }
278
279 ##generates the dhcp range available via CIDR
280 ##uses first 8 IPs, after 1st IP
281 ##params: cidr
282 function generate_dhcp_range {
283   local dhcp_range_start dhcp_range_end first_ip
284   #first_ip=$(ipcalc  -nb $1 | grep HostMin: | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
285   first_ip=$(ipcalc -nmpb $1 | grep NETWORK= | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
286   if [ -z "$first_ip" ]; then
287     return 1
288   fi
289   first_ip=$(increment_ip ${first_ip} 1)
290   dhcp_range_start=$(increment_ip ${first_ip} 1)
291   dhcp_range_end=$(increment_ip ${dhcp_range_start} 8)
292   echo "${dhcp_range_start},${dhcp_range_end}"
293 }
294
295 ##finds the introspection range available via interface
296 ##uses 8 IPs, after the first 10 IPs
297 ##params: interface
298 function find_introspection_range {
299   local inspect_range_start inspect_range_end interface_ip
300   if [ -z "$1"  ]; then
301     return 1
302   fi
303   interface_ip=$(find_ip $1)
304   if [ -z "$interface_ip" ]; then
305     return 1
306   fi
307   inspect_range_start=$(increment_ip ${interface_ip} 10)
308   inspect_range_end=$(increment_ip ${inspect_range_start} 8)
309   echo "${inspect_range_start},${inspect_range_end}"
310 }
311
312 ##generate the introspection range available via CIDR
313 ##uses 8 IPs, after the first 10 IPs
314 ##params: cidr
315 function generate_introspection_range {
316   local inspect_range_start inspect_range_end first_ip
317   #first_ip=$(ipcalc  -nb $1 | grep HostMin: | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
318   first_ip=$(ipcalc -nmpb $1 | grep NETWORK= | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
319   if [ -z "$first_ip" ]; then
320     return 1
321   fi
322   first_ip=$(increment_ip ${first_ip} 1)
323   inspect_range_start=$(increment_ip ${first_ip} 10)
324   inspect_range_end=$(increment_ip ${inspect_range_start} 8)
325   echo "${inspect_range_start},${inspect_range_end}"
326 }
327
328 ##finds the floating ip range available via interface
329 ##uses last 20 IPs of a subnet, minus last IP
330 ##params: interface
331 function find_floating_ip_range {
332   local float_range_start float_range_end interface_ip subnet_mask
333   if [ -z "$1"  ]; then
334     return 1
335   fi
336   interface_ip=$(find_ip $1)
337   subnet_mask=$(find_netmask $1)
338   if [[ -z "$interface_ip" || -z "$subnet_mask" ]]; then
339     return 1
340   fi
341   float_range_end=$(find_last_ip_subnet ${interface_ip} ${subnet_mask})
342   float_range_end=$(subtract_ip ${float_range_end} 1)
343   float_range_start=$(subtract_ip ${float_range_end} 19)
344   echo "${float_range_start},${float_range_end}"
345 }
346
347 ##generate the floating range available via CIDR
348 ##uses last 20 IPs of subnet, minus last IP
349 ##params: cidr
350 function generate_floating_ip_range {
351   local float_range_start float_range_end last_ip
352   #last_ip=$(ipcalc  -nb $1 | grep HostMax: | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
353   last_ip=$(ipcalc -nmpb $1 | grep BROADCAST= | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")
354   if [ -z "$last_ip" ]; then
355     return 1
356   fi
357   last_ip=$(subtract_ip ${last_ip} 2)
358   float_range_start=$(subtract_ip ${last_ip} 19)
359   float_range_end=${last_ip}
360   echo "${float_range_start},${float_range_end}"
361 }
362
363 ##attach interface to OVS and set the network config correctly
364 ##params: bride to attach to, interface to attach, network type (optional)
365 ##public indicates attaching to a public interface
366 function attach_interface_to_ovs {
367   local bridge interface
368   local if_ip if_mask if_gw if_file ovs_file if_prefix
369
370   if [[ -z "$1" || -z "$2" ]]; then
371     return 1
372   else
373     bridge=$1
374     interface=$2
375   fi
376
377   if ovs-vsctl list-ports ${bridge} | grep ${interface}; then
378     return 0
379   fi
380
381   if_file=/etc/sysconfig/network-scripts/ifcfg-${interface}
382   ovs_file=/etc/sysconfig/network-scripts/ifcfg-${bridge}
383
384   if [ -e "$if_file" ]; then
385     if_ip=$(sed -n 's/^IPADDR=\(.*\)$/\1/p' ${if_file})
386     if_mask=$(sed -n 's/^NETMASK=\(.*\)$/\1/p' ${if_file})
387     if_gw=$(sed -n 's/^GATEWAY=\(.*\)$/\1/p' ${if_file})
388   else
389     echo "ERROR: ifcfg file missing for ${interface}"
390     return 1
391   fi
392
393   if [ -z "$if_mask" ]; then
394     # we can look for PREFIX here, then convert it to NETMASK
395     if_prefix=$(sed -n 's/^PREFIX=\(.*\)$/\1/p' ${if_file})
396     if_mask=$(prefix2mask ${if_prefix})
397   fi
398
399   if [[ -z "$if_ip" || -z "$if_mask" ]]; then
400     echo "ERROR: IPADDR or NETMASK/PREFIX missing for ${interface}"
401     return 1
402   elif [[ -z "$if_gw" && "$3" == "public_network" ]]; then
403     echo "ERROR: GATEWAY missing for ${interface}, which is public"
404     return 1
405   fi
406
407   # move old config file to .orig
408   mv -f ${if_file} ${if_file}.orig
409   echo "DEVICE=${interface}
410 DEVICETYPE=ovs
411 TYPE=OVSPort
412 PEERDNS=no
413 BOOTPROTO=static
414 NM_CONTROLLED=no
415 ONBOOT=yes
416 OVS_BRIDGE=${bridge}
417 PROMISC=yes" > ${if_file}
418
419   if [ -z ${if_gw} ]; then
420   # create bridge cfg
421   echo "DEVICE=${bridge}
422 DEVICETYPE=ovs
423 IPADDR=${if_ip}
424 NETMASK=${if_mask}
425 BOOTPROTO=static
426 ONBOOT=yes
427 TYPE=OVSBridge
428 PROMISC=yes
429 PEERDNS=no" > ${ovs_file}
430
431   else
432     echo "DEVICE=${bridge}
433 DEVICETYPE=ovs
434 IPADDR=${if_ip}
435 NETMASK=${if_mask}
436 BOOTPROTO=static
437 ONBOOT=yes
438 TYPE=OVSBridge
439 PROMISC=yes
440 GATEWAY=${if_gw}
441 PEERDNS=no" > ${ovs_file}
442   fi
443
444   sudo systemctl restart network
445 }
446
447 ##detach interface from OVS and set the network config correctly
448 ##params: bridge to detach from
449 ##assumes only 1 real interface attached to OVS
450 function detach_interface_from_ovs {
451   local bridge
452   local port_output ports_no_orig
453   local net_path
454   local if_ip if_mask if_gw if_prefix
455
456   net_path=/etc/sysconfig/network-scripts/
457   if [[ -z "$1" ]]; then
458     return 1
459   else
460     bridge=$1
461   fi
462
463   # if no interfaces attached then return
464   if ! ovs-vsctl list-ports ${bridge} | grep -Ev "vnet[0-9]*"; then
465     return 0
466   fi
467
468   # look for .orig ifcfg files  to use
469   port_output=$(ovs-vsctl list-ports ${bridge} | grep -Ev "vnet[0-9]*")
470   while read -r line; do
471     if [ -z "$line" ]; then
472       continue
473     elif [ -e ${net_path}/ifcfg-${line}.orig ]; then
474       mv -f ${net_path}/ifcfg-${line}.orig ${net_path}/ifcfg-${line}
475     elif [ -e ${net_path}/ifcfg-${bridge} ]; then
476       if_ip=$(sed -n 's/^IPADDR=\(.*\)$/\1/p' ${if_file})
477       if_mask=$(sed -n 's/^NETMASK=\(.*\)$/\1/p' ${if_file})
478       if_gw=$(sed -n 's/^GATEWAY=\(.*\)$/\1/p' ${if_file})
479
480       if [ -z "$if_mask" ]; then
481         if_prefix=$(sed -n 's/^PREFIX=\(.*\)$/\1/p' ${if_file})
482         if_mask=$(prefix2mask ${if_prefix})
483       fi
484
485       if [[ -z "$if_ip" || -z "$if_mask" ]]; then
486         echo "ERROR: IPADDR or PREFIX/NETMASK missing for ${bridge} and no .orig file for interface ${line}"
487         return 1
488       fi
489
490       if [ -z ${if_gw} ]; then
491         # create if cfg
492         echo "DEVICE=${line}
493 IPADDR=${if_ip}
494 NETMASK=${if_mask}
495 BOOTPROTO=static
496 ONBOOT=yes
497 TYPE=Ethernet
498 NM_CONTROLLED=no
499 PEERDNS=no" > ${net_path}/ifcfg-${line}
500       else
501         echo "DEVICE=${line}
502 IPADDR=${if_ip}
503 NETMASK=${if_mask}
504 BOOTPROTO=static
505 ONBOOT=yes
506 TYPE=Ethernet
507 NM_CONTROLLED=no
508 GATEWAY=${if_gw}
509 PEERDNS=no" > ${net_path}/ifcfg-${line}
510       fi
511       break
512     else
513       echo "ERROR: Real interface ${line} attached to bridge, but no interface or ${bridge} ifcfg file exists"
514       return 1
515     fi
516
517   done <<< "$port_output"
518
519   # modify the bridge ifcfg file
520   # to remove IP params
521   sudo sed -i 's/IPADDR=.*//' ${net_path}/ifcfg-${bridge}
522   sudo sed -i 's/NETMASK=.*//' ${net_path}/ifcfg-${bridge}
523   sudo sed -i 's/GATEWAY=.*//' ${net_path}/ifcfg-${bridge}
524
525   sudo systemctl restart network
526 }
527
528 # Update iptables rule for external network reach internet
529 # for virtual deployments
530 # params: external_cidr
531 function configure_undercloud_nat {
532   local external_cidr
533   if [[ -z "$1" ]]; then
534     return 1
535   else
536     external_cidr=$1
537   fi
538
539   ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" <<EOI
540 iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
541 iptables -t nat -A POSTROUTING -s ${external_cidr} -o eth0 -j MASQUERADE
542 iptables -A FORWARD -i eth2 -j ACCEPT
543 iptables -A FORWARD -s ${external_cidr} -m state --state ESTABLISHED,RELATED -j ACCEPT
544 service iptables save
545 EOI
546 }