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