9d5d9d28c3bb389cd488cfbd237633be3bf66846
[apex.git] / ci / deploy.sh
1 #!/bin/bash
2
3 # Deploy script to install provisioning server for OPNFV Apex
4 # author: Dan Radez (dradez@redhat.com)
5 # author: Tim Rozet (trozet@redhat.com)
6 #
7 # Based on RDO Manager http://www.rdoproject.org
8
9 set -e
10
11 ##VARIABLES
12 if [ "$TERM" != "unknown" ]; then
13   reset=$(tput sgr0)
14   blue=$(tput setaf 4)
15   red=$(tput setaf 1)
16   green=$(tput setaf 2)
17 else
18   reset=""
19   blue=""
20   red=""
21   green=""
22 fi
23
24 vm_index=4
25 ha_enabled="TRUE"
26 ping_site="8.8.8.8"
27 ntp_server="pool.ntp.org"
28 net_isolation_enabled="TRUE"
29
30 declare -i CNT
31 declare UNDERCLOUD
32 declare -A deploy_options_array
33 declare -A NET_MAP
34
35 SSH_OPTIONS=(-o StrictHostKeyChecking=no -o GlobalKnownHostsFile=/dev/null -o UserKnownHostsFile=/dev/null -o LogLevel=error)
36 DEPLOY_OPTIONS=""
37 RESOURCES=/var/opt/opnfv/stack
38 CONFIG=/var/opt/opnfv
39 INSTACKENV=$CONFIG/instackenv.json
40 OPNFV_NETWORK_TYPES="admin_network private_network public_network storage_network"
41 # Netmap used to map networks to OVS bridge names
42 NET_MAP['admin_network']="brbm"
43 NET_MAP['private_network']="brbm1"
44 NET_MAP['public_network']="brbm2"
45 NET_MAP['storage_network']="brbm3"
46
47 ##LIBRARIES
48 source $CONFIG/lib/common-functions.sh
49
50 ##FUNCTIONS
51 ##translates yaml into variables
52 ##params: filename, prefix (ex. "config_")
53 ##usage: parse_yaml opnfv_ksgen_settings.yml "config_"
54 parse_yaml() {
55    local prefix=$2
56    local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
57    sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
58         -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p"  $1 |
59    awk -F$fs '{
60       indent = length($1)/2;
61       vname[indent] = $2;
62       for (i in vname) {if (i > indent) {delete vname[i]}}
63       if (length($3) > 0) {
64          vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
65          printf("%s%s%s=%s\n", "'$prefix'",vn, $2, $3);
66       }
67    }'
68 }
69
70 ##checks if prefix exists in string
71 ##params: string, prefix
72 ##usage: contains_prefix "deploy_setting_launcher=1" "deploy_setting"
73 contains_prefix() {
74   local mystr=$1
75   local prefix=$2
76   if echo $mystr | grep -E "^$prefix.*$" > /dev/null; then
77     return 0
78   else
79     return 1
80   fi
81 }
82 ##parses variable from a string with '='
83 ##and removes global prefix
84 ##params: string, prefix
85 ##usage: parse_setting_var 'deploy_myvar=2' 'deploy_'
86 parse_setting_var() {
87   local mystr=$1
88   local prefix=$2
89   if echo $mystr | grep -E "^.+\=" > /dev/null; then
90     echo $(echo $mystr | grep -Eo "^.+\=" | tr -d '=' |  sed 's/^'"$prefix"'//')
91   else
92     return 1
93   fi
94 }
95 ##parses value from a string with '='
96 ##params: string
97 ##usage: parse_setting_value
98 parse_setting_value() {
99   local mystr=$1
100   echo $(echo $mystr | grep -Eo "\=.*$" | tr -d '=')
101 }
102 ##parses network settings yaml into globals
103 parse_network_settings() {
104   local required_network_settings="cidr"
105   local common_optional_network_settings="usable_ip_range"
106   local admin_network_optional_settings="provisioner_ip dhcp_range introspection_range"
107   local public_network_optional_settings="floating_ip_range gateway provisioner_ip"
108   local nic_value cidr
109
110   eval $(parse_yaml ${NETSETS})
111   for network in ${OPNFV_NETWORK_TYPES}; do
112     if [[ $(eval echo \${${network}_enabled}) == 'true' ]]; then
113       enabled_network_list+="${network} "
114     elif [ "${network}" == 'admin_network' ]; then
115       echo -e "${red}ERROR: You must enable admin_network and configure it explicitly or use auto-detection${reset}"
116       exit 1
117     elif [ "${network}" == 'public_network' ]; then
118       echo -e "${red}ERROR: You must enable public_network and configure it explicitly or use auto-detection${reset}"
119       exit 1
120     else
121       echo -e "${blue}INFO: Network: ${network} is disabled, will collapse into admin_network"
122     fi
123   done
124
125   # check for enabled network values
126   for enabled_network in ${enabled_network_list}; do
127     # detect required settings first to continue
128     echo -e "${blue}INFO: Detecting Required settings for: ${enabled_network}${reset}"
129     for setting in ${required_network_settings}; do
130       eval "setting_value=\${${enabled_network}_${setting}}"
131       if [ -z "${setting_value}" ]; then
132         # if setting is missing we try to autodetect
133         eval "nic_value=\${${enabled_network}_bridged_interface}"
134         if [ -n "$nic_value" ]; then
135           setting_value=$(eval find_${setting} ${nic_value})
136           if [ -n "$setting_value" ]; then
137             eval "${enabled_network}_${setting}=${setting_value}"
138             echo -e "${blue}INFO: Auto-detection: ${enabled_network}_${setting}: ${setting_value}${reset}"
139           else
140             echo -e "${red}ERROR: Auto-detection failed: ${setting} not found using interface: ${nic_value}${reset}"
141             exit 1
142           fi
143         else
144           echo -e "${red}ERROR: Required setting: ${setting} not found, and bridge interface not provided\
145 for Auto-detection${reset}"
146           exit 1
147         fi
148       else
149         echo -e "${blue}INFO: ${enabled_network}_${setting}: ${setting_value}${reset}"
150       fi
151     done
152     echo -e "${blue}INFO: Detecting Common settings for: ${enabled_network}${reset}"
153     # detect optional common settings
154     # these settings can be auto-generated if missing
155     for setting in ${common_optional_network_settings}; do
156       eval "setting_value=\${${enabled_network}_${setting}}"
157       if [ -z "${setting_value}" ]; then
158         setting_value=$(eval find_${setting} ${nic_value})
159         if [ -n "$setting_value" ]; then
160           eval "${enabled_network}_${setting}=${setting_value}"
161           echo -e "${blue}INFO: Auto-detection: ${enabled_network}_${setting}: ${setting_value}${reset}"
162         else
163           # if Auto-detection fails we can auto-generate with CIDR
164           eval "cidr=\${${enabled_network}_cidr}"
165           setting_value=$(eval generate_${setting} ${cidr})
166           if [ -n "$setting_value" ]; then
167             eval "${enabled_network}_${setting}=${setting_value}"
168             echo -e "${blue}INFO: Auto-generated: ${enabled_network}_${setting}: ${setting_value}${reset}"
169           else
170             echo -e "${red}ERROR: Auto-generation failed: ${setting} not found${reset}"
171             exit 1
172           fi
173         fi
174       else
175         echo -e "${blue}INFO: ${enabled_network}_${setting}: ${setting_value}${reset}"
176       fi
177     done
178     echo -e "${blue}INFO: Detecting Network Specific settings for: ${enabled_network}${reset}"
179     # detect network specific settings
180     if [ -n $(eval echo \${${network}_optional_settings}) ]; then
181       eval "network_specific_settings=\${${enabled_network}_optional_settings}"
182       for setting in ${network_specific_settings}; do
183         eval "setting_value=\${${enabled_network}_${setting}}"
184         if [ -z "${setting_value}" ]; then
185           setting_value=$(eval find_${setting} ${nic_value})
186           if [ -n "$setting_value" ]; then
187             eval "${enabled_network}_${setting}=${setting_value}"
188             echo -e "${blue}INFO: Auto-detection: ${enabled_network}_${setting}: ${setting_value}${reset}"
189           else
190             eval "cidr=\${${enabled_network}_cidr}"
191             setting_value=$(eval generate_${setting} ${cidr})
192             if [ -n "$setting_value" ]; then
193               eval "${enabled_network}_${setting}=${setting_value}"
194               echo -e "${blue}INFO: Auto-generated: ${enabled_network}_${setting}: ${setting_value}${reset}"
195             else
196               echo -e "${red}ERROR: Auto-generation failed: ${setting} not found${reset}"
197               exit 1
198             fi
199           fi
200         else
201           echo -e "${blue}INFO: ${enabled_network}_${setting}: ${setting_value}${reset}"
202         fi
203       done
204     fi
205   done
206 }
207 ##parses deploy settings yaml into globals and options array
208 ##params: none
209 ##usage:  parse_deploy_settings
210 parse_deploy_settings() {
211   local global_prefix="deploy_global_params_"
212   local options_prefix="deploy_deploy_options_"
213   local myvar myvalue
214   local settings=$(parse_yaml $DEPLOY_SETTINGS_FILE "deploy_")
215
216   for this_setting in $settings; do
217     if contains_prefix $this_setting $global_prefix; then
218       myvar=$(parse_setting_var $this_setting $global_prefix)
219       if [ -z "$myvar" ]; then
220         echo -e "${red}ERROR: while parsing ${DEPLOY_SETTINGS_FILE} for setting: ${this_setting}${reset}"
221       fi
222       myvalue=$(parse_setting_value $this_setting)
223       # Do not override variables set by cmdline
224       if [ -z "$(eval echo \$$myvar)" ]; then
225         eval "$myvar=\$myvalue"
226         echo -e "${blue}Global parameter set: ${myvar}:${myvalue}${reset}"
227       else
228         echo -e "${blue}Global parameter already set: ${myvar}${reset}"
229       fi
230     elif contains_prefix $this_setting $options_prefix; then
231       myvar=$(parse_setting_var $this_setting $options_prefix)
232       if [ -z "$myvar" ]; then
233         echo -e "${red}ERROR: while parsing ${DEPLOY_SETTINGS_FILE} for setting: ${this_setting}${reset}"
234       fi
235       myvalue=$(parse_setting_value $this_setting)
236       deploy_options_array[$myvar]=$myvalue
237       echo -e "${blue}Deploy option set: ${myvar}:${myvalue}${reset}"
238     fi
239   done
240 }
241 ##parses baremetal yaml settings into compatible json
242 ##writes the json to $CONFIG/instackenv_tmp.json
243 ##params: none
244 ##usage: parse_inventory_file
245 parse_inventory_file() {
246   local inventory=$(parse_yaml $INVENTORY_FILE)
247   local node_list
248   local node_prefix="node"
249   local node_count=0
250   local node_total
251   local inventory_list
252
253   # detect number of nodes
254   for entry in $inventory; do
255     if echo $entry | grep -Eo "^nodes_node[0-9]+_" > /dev/null; then
256       this_node=$(echo $entry | grep -Eo "^nodes_node[0-9]+_")
257       if [[ $inventory_list != *"$this_node"* ]]; then
258         inventory_list+="$this_node "
259       fi
260     fi
261   done
262
263   inventory_list=$(echo $inventory_list | sed 's/ $//')
264
265   for node in $inventory_list; do
266     ((node_count+=1))
267   done
268
269   node_total=$node_count
270
271   if [[ "$node_total" -lt 5 && ha_enabled == "TRUE" ]]; then
272     echo -e "${red}ERROR: You must provide at least 5 nodes for HA baremetal deployment${reset}"
273     exit 1
274   elif [[ "$node_total" -lt 2 ]]; then
275     echo -e "${red}ERROR: You must provide at least 2 nodes for non-HA baremetal deployment${reset}"
276     exit 1
277   fi
278
279   eval $(parse_yaml $INVENTORY_FILE)
280
281   instack_env_output="
282 {
283  \"nodes\" : [
284
285 "
286   node_count=0
287   for node in $inventory_list; do
288     ((node_count+=1))
289     node_output="
290         {
291           \"pm_password\": \"$(eval echo \${${node}ipmi_pass})\",
292           \"pm_type\": \"pxe_ipmitool\",
293           \"mac\": [
294             \"$(eval echo \${${node}mac_address})\"
295           ],
296           \"cpu\": \"$(eval echo \${${node}cpus})\",
297           \"memory\": \"$(eval echo \${${node}memory})\",
298           \"disk\": \"$(eval echo \${${node}disk})\",
299           \"arch\": \"$(eval echo \${${node}arch})\",
300           \"pm_user\": \"$(eval echo \${${node}ipmi_user})\",
301           \"pm_addr\": \"$(eval echo \${${node}ipmi_ip})\",
302           \"capabilities\": \"$(eval echo \${${node}capabilities})\"
303 "
304     instack_env_output+=${node_output}
305     if [ $node_count -lt $node_total ]; then
306       instack_env_output+="        },"
307     else
308       instack_env_output+="        }"
309     fi
310   done
311
312   instack_env_output+='
313   ]
314 }
315 '
316   #Copy instackenv.json to undercloud for baremetal
317   echo -e "{blue}Parsed instackenv JSON:\n${instack_env_output}${reset}"
318   ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" <<EOI
319 cat > instackenv.json << EOF
320 $instack_env_output
321 EOF
322 EOI
323
324 }
325 ##verify internet connectivity
326 #params: none
327 function verify_internet {
328   if ping -c 2 $ping_site > /dev/null; then
329     if ping -c 2 www.google.com > /dev/null; then
330       echo "${blue}Internet connectivity detected${reset}"
331       return 0
332     else
333       echo "${red}Internet connectivity detected, but DNS lookup failed${reset}"
334       return 1
335     fi
336   else
337     echo "${red}No internet connectivity detected${reset}"
338     return 1
339   fi
340 }
341
342 ##download dependencies if missing and configure host
343 #params: none
344 function configure_deps {
345   if ! verify_internet; then
346     echo "${red}Will not download dependencies${reset}"
347     internet=false
348   fi
349
350   # verify ip forwarding
351   if sysctl net.ipv4.ip_forward | grep 0; then
352     sudo sysctl -w net.ipv4.ip_forward=1
353     sudo sh -c "echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf"
354   fi
355
356   # ensure no dhcp server is running on jumphost
357   if ! sudo systemctl status dhcpd | grep dead; then
358     echo "${red}WARN: DHCP Server detected on jumphost, disabling...${reset}"
359     sudo systemctl stop dhcpd
360     sudo systemctl disable dhcpd
361   fi
362
363   # ensure networks are configured
364   systemctl start openvswitch
365
366   # If flat we only use admin network
367   if [[ "$net_isolation_enabled" == "FALSE" ]]; then
368     virsh_enabled_networks="admin_network"
369   # For baremetal we only need to create/attach instack to admin and public
370   elif [ "$virtual" == "FALSE" ]; then
371     virsh_enabled_networks="admin_network public_network"
372   else
373     virsh_enabled_neworks=$enabled_network_list
374   fi
375
376   for network in ${OPNFV_NETWORK_TYPES}; do
377     ovs-vsctl list-br | grep ${NET_MAP[$network]} > /dev/null || ovs-vsctl add-br ${NET_MAP[$network]}
378     virsh net-list --all | grep ${NET_MAP[$network]} > /dev/null || virsh net-create $CONFIG/${NET_MAP[$network]}-net.xml
379     virsh net-list | grep -E "${NET_MAP[$network]}\s+active" > /dev/null || virsh net-start ${NET_MAP[$network]}
380   done
381
382   echo -e "${blue}INFO: Bridges set: ${reset}"
383   ovs-vsctl list-br
384   echo -e "${blue}INFO: virsh networks set: ${reset}"
385   virsh net-list
386
387   if [[ -z "$virtual" || "$virtual" == "FALSE" ]]; then
388     # bridge interfaces to correct OVS instances for baremetal deployment
389     for network in ${enabled_network_list}; do
390       this_interface=$(eval echo \${${network}_bridged_interface})
391       # check if this a bridged interface for this network
392       if [[ ! -z "$this_interface" || "$this_interface" != "none" ]]; then
393         if ! attach_interface_to_ovs ${NET_MAP[$network]} ${this_interface} ${network}; then
394           echo -e "${red}ERROR: Unable to bridge interface ${this_interface} to bridge ${NET_MAP[$network]} for enabled network: ${network}${reset}"
395           exit 1
396         else
397           echo -e "${blue}INFO: Interface ${this_interface} bridged to bridge ${NET_MAP[$network]} for enabled network: ${network}${reset}"
398         fi
399       else
400         echo "${red}ERROR: Unable to determine interface to bridge to for enabled network: ${network}${reset}"
401         exit 1
402       fi
403     done
404   fi
405
406   # ensure storage pool exists and is started
407   virsh pool-list --all | grep default > /dev/null || virsh pool-create $CONFIG/default-pool.xml
408   virsh pool-list | grep -Eo "default\s+active" > /dev/null || virsh pool-start default
409
410   if virsh net-list | grep default > /dev/null; then
411     num_ints_same_subnet=$(ip addr show | grep "inet 192.168.122" | wc -l)
412     if [ "$num_ints_same_subnet" -gt 1 ]; then
413       virsh net-destroy default
414       ##go edit /etc/libvirt/qemu/networks/default.xml
415       sed -i 's/192.168.122/192.168.123/g' /etc/libvirt/qemu/networks/default.xml
416       sed -i 's/192.168.122/192.168.123/g' instackenv-virt.json
417       sleep 5
418       virsh net-start default
419       virsh net-autostart default
420     fi
421   fi
422
423   if ! egrep '^flags.*(vmx|svm)' /proc/cpuinfo > /dev/null; then
424     echo "${red}virtualization extensions not found, kvm kernel module insertion may fail.\n  \
425 Are you sure you have enabled vmx in your bios or hypervisor?${reset}"
426   fi
427
428   if ! lsmod | grep kvm > /dev/null; then modprobe kvm; fi
429   if ! lsmod | grep kvm_intel > /dev/null; then modprobe kvm_intel; fi
430
431   if ! lsmod | grep kvm > /dev/null; then
432     echo "${red}kvm kernel modules not loaded!${reset}"
433     return 1
434   fi
435
436   ##sshkeygen for root
437   if [ ! -e ~/.ssh/id_rsa.pub ]; then
438     ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa
439   fi
440
441   echo "${blue}All dependencies installed and running${reset}"
442 }
443
444 ##verify vm exists, an has a dhcp lease assigned to it
445 ##params: none
446 function setup_instack_vm {
447   if ! virsh list --all | grep instack > /dev/null; then
448       #virsh vol-create default instack.qcow2.xml
449       virsh define $CONFIG/instack.xml
450
451       #Upload instack image
452       #virsh vol-create default --file instack.qcow2.xml
453       virsh vol-create-as default instack.qcow2 30G --format qcow2
454
455       ### this doesn't work for some reason I was getting hangup events so using cp instead
456       #virsh vol-upload --pool default --vol instack.qcow2 --file $CONFIG/stack/instack.qcow2
457       #2015-12-05 12:57:20.569+0000: 8755: info : libvirt version: 1.2.8, package: 16.el7_1.5 (CentOS BuildSystem <http://bugs.centos.org>, 2015-11-03-13:56:46, worker1.bsys.centos.org)
458       #2015-12-05 12:57:20.569+0000: 8755: warning : virKeepAliveTimerInternal:143 : No response from client 0x7ff1e231e630 after 6 keepalive messages in 35 seconds
459       #2015-12-05 12:57:20.569+0000: 8756: warning : virKeepAliveTimerInternal:143 : No response from client 0x7ff1e231e630 after 6 keepalive messages in 35 seconds
460       #error: cannot close volume instack.qcow2
461       #error: internal error: received hangup / error event on socket
462       #error: Reconnected to the hypervisor
463
464       instack_dst=/var/lib/libvirt/images/instack.qcow2
465       cp -f $RESOURCES/instack.qcow2 $instack_dst
466
467       # resize instack machine
468       echo "Checking if instack needs to be resized..."
469       instack_size=$(LIBGUESTFS_BACKEND=direct virt-filesystems --long -h --all -a $instack_dst |grep device | grep -Eo "[0-9\.]+G" | sed -n 's/\([0-9][0-9]*\).*/\1/p')
470       if [ "$instack_size" -lt 30 ]; then
471         qemu-img resize /var/lib/libvirt/images/instack.qcow2 +25G
472         LIBGUESTFS_BACKEND=direct virt-resize --expand /dev/sda1 $RESOURCES/instack.qcow2 $instack_dst
473         LIBGUESTFS_BACKEND=direct virt-customize -a $instack_dst --run-command 'xfs_growfs -d /dev/sda1 || true'
474         new_size=$(LIBGUESTFS_BACKEND=direct virt-filesystems --long -h --all -a $instack_dst |grep filesystem | grep -Eo "[0-9\.]+G" | sed -n 's/\([0-9][0-9]*\).*/\1/p')
475         if [ "$new_size" -lt 30 ]; then
476           echo "Error resizing instack machine, disk size is ${new_size}"
477           exit 1
478         else
479           echo "instack successfully resized"
480         fi
481       else
482         echo "skipped instack resize, upstream is large enough"
483       fi
484
485   else
486       echo "Found Instack VM, using existing VM"
487   fi
488
489   # if the VM is not running update the authkeys and start it
490   if ! virsh list | grep instack > /dev/null; then
491     echo "Injecting ssh key to instack VM"
492     virt-customize -c qemu:///system -d instack --run-command "mkdir /root/.ssh/" \
493         --upload ~/.ssh/id_rsa.pub:/root/.ssh/authorized_keys \
494         --run-command "chmod 600 /root/.ssh/authorized_keys && restorecon /root/.ssh/authorized_keys" \
495         --run-command "cp /root/.ssh/authorized_keys /home/stack/.ssh/" \
496         --run-command "chown stack:stack /home/stack/.ssh/authorized_keys && chmod 600 /home/stack/.ssh/authorized_keys"
497     virsh start instack
498   fi
499
500   sleep 3 # let DHCP happen
501
502   CNT=10
503   echo -n "${blue}Waiting for instack's dhcp address${reset}"
504   while ! grep instack /var/lib/libvirt/dnsmasq/default.leases > /dev/null && [ $CNT -gt 0 ]; do
505       echo -n "."
506       sleep 3
507       CNT=CNT-1
508   done
509
510   # get the instack VM IP
511   UNDERCLOUD=$(grep instack /var/lib/libvirt/dnsmasq/default.leases | awk '{print $3}' | head -n 1)
512   if [ -z "$UNDERCLOUD" ]; then
513     #if not found then dnsmasq may be using leasefile-ro
514     instack_mac=$(virsh domiflist instack | grep default | \
515                   grep -Eo "[0-9a-f\]+:[0-9a-f\]+:[0-9a-f\]+:[0-9a-f\]+:[0-9a-f\]+:[0-9a-f\]+")
516     UNDERCLOUD=$(/usr/sbin/arp -e | grep ${instack_mac} | awk {'print $1'})
517
518     if [ -z "$UNDERCLOUD" ]; then
519       echo "\n\nNever got IP for Instack. Can Not Continue."
520       exit 1
521     else
522       echo -e "${blue}\rInstack VM has IP $UNDERCLOUD${reset}"
523     fi
524   else
525      echo -e "${blue}\rInstack VM has IP $UNDERCLOUD${reset}"
526   fi
527
528   CNT=10
529   echo -en "${blue}\rValidating instack VM connectivity${reset}"
530   while ! ping -c 1 $UNDERCLOUD > /dev/null && [ $CNT -gt 0 ]; do
531       echo -n "."
532       sleep 3
533       CNT=$CNT-1
534   done
535   if [ "$CNT" -eq 0 ]; then
536       echo "Failed to contact Instack. Can Not Continue"
537       exit 1
538   fi
539   CNT=10
540   while ! ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" "echo ''" 2>&1> /dev/null && [ $CNT -gt 0 ]; do
541       echo -n "."
542       sleep 3
543       CNT=$CNT-1
544   done
545   if [ "$CNT" -eq 0 ]; then
546       echo "Failed to connect to Instack. Can Not Continue"
547       exit 1
548   fi
549
550   # extra space to overwrite the previous connectivity output
551   echo -e "${blue}\r                                                                 ${reset}"
552
553   #add the instack public interface if net isolation is enabled (more than just admin network)
554   if [[ "$net_isolation_enabled" == "TRUE" ]]; then
555     virsh attach-interface --domain instack --type network --source ${NET_MAP['public_network']} --model rtl8139 --config --live
556     sleep 1
557     ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" "if ! ip a s eth2 | grep ${public_network_provisioner_ip} > /dev/null; then ip a a ${public_network_provisioner_ip}/${public_network_cidr##*/} dev eth2; ip link set up dev eth2; fi"
558   fi
559   # ssh key fix for stack user
560   ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" "restorecon -r /home/stack"
561 }
562
563 ##Create virtual nodes in virsh
564 ##params: none
565 function setup_virtual_baremetal {
566   for i in $(seq 0 $vm_index); do
567     if ! virsh list --all | grep baremetalbrbm_brbm1_brbm2_brbm3_${i} > /dev/null; then
568       if [ ! -e $CONFIG/baremetalbrbm_brbm1_brbm2_brbm3_${i}.xml ]; then
569         define_virtual_node baremetalbrbm_brbm1_brbm2_brbm3_${i}
570       fi
571       # Fix for ramdisk using wrong pxeboot interface
572       # TODO: revisit this and see if there's a more proper fix
573       sed -i "/^\s*<source network='brbm2'\/>/{
574         N
575         s/^\(.*\)virtio\(.*\)$/\1rtl8139\2/
576         }" $CONFIG/baremetalbrbm_brbm1_brbm2_brbm3_${i}.xml
577       virsh define $CONFIG/baremetalbrbm_brbm1_brbm2_brbm3_${i}.xml
578     else
579       echo "Found Baremetal ${i} VM, using existing VM"
580     fi
581     virsh vol-list default | grep baremetalbrbm_brbm1_brbm2_brbm3_${i} 2>&1> /dev/null || virsh vol-create-as default baremetalbrbm_brbm1_brbm2_brbm3_${i}.qcow2 40G --format qcow2
582   done
583
584 }
585
586 ##Set network-environment settings
587 ##params: network-environment file to edit
588 function configure_network_environment {
589   sed -i '/ControlPlaneSubnetCidr/c\\  ControlPlaneSubnetCidr: "'${admin_network_cidr##*/}'"' $1
590   sed -i '/ControlPlaneDefaultRoute/c\\  ControlPlaneDefaultRoute: '${admin_network_provisioner_ip}'' $1
591   sed -i '/ExternalNetCidr/c\\  ExternalNetCidr: '${public_network_cidr}'' $1
592   sed -i "/ExternalAllocationPools/c\\  ExternalAllocationPools: [{'start': '${public_network_usable_ip_range%%,*}', 'end': '${public_network_usable_ip_range##*,}'}]" $1
593   sed -i '/ExternalInterfaceDefaultRoute/c\\  ExternalInterfaceDefaultRoute: '${public_network_gateway}'' $1
594   sed -i '/EC2MetadataIp/c\\  EC2MetadataIp: '${admin_network_provisioner_ip}'' $1
595 }
596 ##Copy over the glance images and instack json file
597 ##params: none
598 function configure_undercloud {
599
600   echo
601   echo "Copying configuration file and disk images to instack"
602   scp ${SSH_OPTIONS[@]} $RESOURCES/overcloud-full.qcow2 "stack@$UNDERCLOUD":
603   if [[ "$net_isolation_enabled" == "TRUE" ]]; then
604     configure_network_environment $CONFIG/network-environment.yaml
605     echo -e "${blue}Network Environment set for Deployment: ${reset}"
606     cat $CONFIG/network-environment.yaml
607     scp ${SSH_OPTIONS[@]} $CONFIG/network-environment.yaml "stack@$UNDERCLOUD":
608   fi
609   scp ${SSH_OPTIONS[@]} -r $CONFIG/nics/ "stack@$UNDERCLOUD":
610
611   # ensure stack user on instack machine has an ssh key
612   ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" "if [ ! -e ~/.ssh/id_rsa.pub ]; then ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa; fi"
613
614   if [ "$virtual" == "TRUE" ]; then
615
616       # copy the instack vm's stack user's pub key to
617       # root's auth keys so that instack can control
618       # vm power on the hypervisor
619       ssh ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" "cat /home/stack/.ssh/id_rsa.pub" >> /root/.ssh/authorized_keys
620
621       # fix MACs to match new setup
622       for i in $(seq 0 $vm_index); do
623         pyscript="import json
624 data = json.load(open('$CONFIG/instackenv-virt.json'))
625 print data['nodes'][$i]['mac'][0]"
626
627         old_mac=$(python -c "$pyscript")
628         new_mac=$(virsh dumpxml baremetalbrbm_brbm1_brbm2_brbm3_$i | grep "mac address" | cut -d = -f2 | grep -Eo "[0-9a-f:]+")
629         # this doesn't work with multiple vnics on the vms
630         #if [ "$old_mac" != "$new_mac" ]; then
631         #  echo "${blue}Modifying MAC for node from $old_mac to ${new_mac}${reset}"
632         #  sed -i 's/'"$old_mac"'/'"$new_mac"'/' $CONFIG/instackenv-virt.json
633         #fi
634       done
635
636       DEPLOY_OPTIONS+=" --libvirt-type qemu"
637       INSTACKENV=$CONFIG/instackenv-virt.json
638
639       # upload instackenv file to Instack for virtual deployment
640       scp ${SSH_OPTIONS[@]} $INSTACKENV "stack@$UNDERCLOUD":instackenv.json
641   fi
642
643   # allow stack to control power management on the hypervisor via sshkey
644   # only if this is a virtual deployment
645   if [ "$virtual" == "TRUE" ]; then
646       ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" <<EOI
647 while read -r line; do
648   stack_key=\${stack_key}\\\\\\\\n\${line}
649 done < <(cat ~/.ssh/id_rsa)
650 stack_key=\$(echo \$stack_key | sed 's/\\\\\\\\n//')
651 sed -i 's~INSERT_STACK_USER_PRIV_KEY~'"\$stack_key"'~' instackenv.json
652 EOI
653   fi
654
655   # copy stack's ssh key to this users authorized keys
656   ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" "cat /home/stack/.ssh/id_rsa.pub" >> ~/.ssh/authorized_keys
657
658   # disable requiretty for sudo
659   ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" "sed -i 's/Defaults\s*requiretty//'" /etc/sudoers
660
661   # configure undercloud on Undercloud VM
662   echo "Running undercloud configuration."
663   echo "Logging undercloud configuration to instack:/home/stack/apex-undercloud-install.log"
664   ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" << EOI
665 if [[ "$net_isolation_enabled" == "TRUE" ]]; then
666   sed -i 's/#local_ip/local_ip/' undercloud.conf
667   sed -i 's/#network_gateway/network_gateway/' undercloud.conf
668   sed -i 's/#network_cidr/network_cidr/' undercloud.conf
669   sed -i 's/#dhcp_start/dhcp_start/' undercloud.conf
670   sed -i 's/#dhcp_end/dhcp_end/' undercloud.conf
671   sed -i 's/#inspection_iprange/inspection_iprange/' undercloud.conf
672   sed -i 's/#undercloud_debug/undercloud_debug/' undercloud.conf
673
674   openstack-config --set undercloud.conf DEFAULT local_ip ${admin_network_provisioner_ip}/${admin_network_cidr##*/}
675   openstack-config --set undercloud.conf DEFAULT network_gateway ${admin_network_provisioner_ip}
676   openstack-config --set undercloud.conf DEFAULT network_cidr ${admin_network_cidr}
677   openstack-config --set undercloud.conf DEFAULT dhcp_start ${admin_network_dhcp_range%%,*}
678   openstack-config --set undercloud.conf DEFAULT dhcp_end ${admin_network_dhcp_range##*,}
679   openstack-config --set undercloud.conf DEFAULT inspection_iprange ${admin_network_introspection_range}
680   openstack-config --set undercloud.conf DEFAULT undercloud_debug false
681 fi
682
683 openstack undercloud install &> apex-undercloud-install.log
684 sleep 30
685 sudo systemctl restart openstack-glance-api
686 sudo systemctl restart openstack-nova-conductor
687 sudo systemctl restart openstack-nova-compute
688 EOI
689 # WORKAROUND: must restart the above services to fix sync problem with nova compute manager
690 # TODO: revisit and file a bug if necessary. This should eventually be removed
691 # as well as glance api problem
692 echo -e "${blue}INFO: Sleeping 15 seconds while services come back from restart${reset}"
693 sleep 15
694 #TODO Fill in the rest of the network-environment values for other networks
695
696 }
697
698 ##preping it for deployment and launch the deploy
699 ##params: none
700 function undercloud_prep_overcloud_deploy {
701
702   if [[ ${#deploy_options_array[@]} -eq 0 || ${deploy_options_array['sdn_controller']} == 'opendaylight' ]]; then
703     DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/opendaylight.yaml"
704   elif [ ${deploy_options_array['sdn_controller']} == 'opendaylight-external' ]; then
705     DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/opendaylight-external.yaml"
706   elif [ ${deploy_options_array['sdn_controller']} == 'onos' ]; then
707     DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/onos.yaml"
708   elif [ ${deploy_options_array['sdn_controller']} == 'opencontrail' ]; then
709     echo -e "${red}ERROR: OpenContrail is currently unsupported...exiting${reset}"
710     exit 1
711   fi
712
713   # check if HA is enabled
714   if [[ "$ha_enabled" == "TRUE" ]]; then
715      DEPLOY_OPTIONS+=" --control-scale 3 --compute-scale 2"
716      DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/puppet-pacemaker.yaml"
717   fi
718
719   if [[ "$net_isolation_enabled" == "TRUE" ]]; then
720      #DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/network-isolation.yaml"
721      DEPLOY_OPTIONS+=" -e network-environment.yaml"
722   fi
723
724   if [[ "$ha_enabled" == "TRUE" ]] || [[ "$net_isolation_enabled" == "TRUE" ]]; then
725      DEPLOY_OPTIONS+=" --ntp-server $ntp_server"
726   fi
727
728   if [[ ! "$virtual" == "TRUE" ]]; then
729      DEPLOY_OPTIONS+=" --control-flavor control --compute-flavor compute"
730   fi
731
732   ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" <<EOI
733 source stackrc
734 set -o errexit
735 echo "Uploading overcloud glance images"
736 openstack overcloud image upload
737 echo "Configuring undercloud and discovering nodes"
738 openstack baremetal import --json instackenv.json
739 openstack baremetal configure boot
740 openstack baremetal introspection bulk start
741 echo "Configuring flavors"
742 for flavor in baremetal control compute; do
743   echo -e "${blue}INFO: Updating flavor: \${flavor}${reset}"
744   if openstack flavor list | grep \${flavor}; then
745     openstack flavor delete \${flavor}
746   fi
747   openstack flavor create --id auto --ram 4096 --disk 39 --vcpus 1 \${flavor}
748   if ! openstack flavor list | grep \${flavor}; then
749     echo -e "${red}ERROR: Unable to create flavor \${flavor}${reset}"
750   fi
751 done
752 openstack flavor set --property "cpu_arch"="x86_64" --property "capabilities:boot_option"="local" baremetal
753 openstack flavor set --property "cpu_arch"="x86_64" --property "capabilities:boot_option"="local" --property "capabilities:profile"="control" control
754 openstack flavor set --property "cpu_arch"="x86_64" --property "capabilities:boot_option"="local" --property "capabilities:profile"="compute" compute
755 echo "Configuring nameserver on ctlplane network"
756 neutron subnet-update \$(neutron subnet-list | grep -v id | grep -v \\\\-\\\\- | awk {'print \$2'}) --dns-nameserver 8.8.8.8
757 echo "Executing overcloud deployment, this should run for an extended period without output."
758 sleep 60 #wait for Hypervisor stats to check-in to nova
759 openstack overcloud deploy --templates $DEPLOY_OPTIONS
760 EOI
761
762 }
763
764 display_usage() {
765   echo -e "Usage:\n$0 [arguments] \n"
766   echo -e "   -c|--config : Directory to configuration files. Optional.  Defaults to /var/opt/opnfv/ \n"
767   echo -e "   -d|--deploy-settings : Full path to deploy settings yaml file. Optional.  Defaults to null \n"
768   echo -e "   -i|--inventory : Full path to inventory yaml file. Required only for baremetal \n"
769   echo -e "   -n|--net-settings : Full path to network settings file. Optional. \n"
770   echo -e "   -p|--ping-site : site to use to verify IP connectivity. Optional. Defaults to 8.8.8.8 \n"
771   echo -e "   -r|--resources : Directory to deployment resources. Optional.  Defaults to /var/opt/opnfv/stack \n"
772   echo -e "   -v|--virtual : Virtualize overcloud nodes instead of using baremetal. \n"
773   echo -e "   --no-ha : disable High Availability deployment scheme, this assumes a single controller and single compute node \n"
774   echo -e "   --flat : disable Network Isolation and use a single flat network for the underlay network."
775 }
776
777 ##translates the command line parameters into variables
778 ##params: $@ the entire command line is passed
779 ##usage: parse_cmd_line() "$@"
780 parse_cmdline() {
781   echo -e "\n\n${blue}This script is used to deploy the Apex Installer and Provision OPNFV Target System${reset}\n\n"
782   echo "Use -h to display help"
783   sleep 2
784
785   while [ "${1:0:1}" = "-" ]
786   do
787     case "$1" in
788         -h|--help)
789                 display_usage
790                 exit 0
791             ;;
792         -c|--config)
793                 CONFIG=$2
794                 echo "Deployment Configuration Directory Overridden to: $2"
795                 shift 2
796             ;;
797         -d|--deploy-settings)
798                 DEPLOY_SETTINGS_FILE=$2
799                 echo "Deployment Configuration file: $2"
800                 shift 2
801             ;;
802         -i|--inventory)
803                 INVENTORY_FILE=$2
804                 shift 2
805             ;;
806         -n|--net-settings)
807                 NETSETS=$2
808                 echo "Network Settings Configuration file: $2"
809                 shift 2
810             ;;
811         -p|--ping-site)
812                 ping_site=$2
813                 echo "Using $2 as the ping site"
814                 shift 2
815             ;;
816         -r|--resources)
817                 RESOURCES=$2
818                 echo "Deployment Resources Directory Overridden to: $2"
819                 shift 2
820             ;;
821         -v|--virtual)
822                 virtual="TRUE"
823                 echo "Executing a Virtual Deployment"
824                 shift 1
825             ;;
826         --no-ha )
827                 ha_enabled="FALSE"
828                 echo "HA Deployment Disabled"
829                 shift 1
830             ;;
831         --flat )
832                 net_isolation_enabled="FALSE"
833                 echo "Underlay Network Isolation Disabled: using flat configuration"
834                 shift 1
835             ;;
836         *)
837                 display_usage
838                 exit 1
839             ;;
840     esac
841   done
842
843   if [[ ! -z "$NETSETS" && "$net_isolation_enabled" == "FALSE" ]]; then
844     echo -e "${red}INFO: Single flat network requested. Ignoring any network settings!${reset}"
845   elif [[ -z "$NETSETS" && "$net_isolation_enabled" == "TRUE" ]]; then
846     echo -e "${red}ERROR: You must provide a network_settings file with -n or use --flat to force a single flat network{reset}"
847   fi
848
849   if [[ -n "$virtual" && -n "$INVENTORY_FILE" ]]; then
850     echo -e "${red}ERROR: You should not specify an inventory with virtual deployments${reset}"
851     exit 1
852   fi
853
854   if [[ ! -z "$DEPLOY_SETTINGS_FILE" && ! -f "$DEPLOY_SETTINGS_FILE" ]]; then
855     echo -e "${red}ERROR: ${DEPLOY_SETTINGS_FILE} does not exist! Exiting...${reset}"
856     exit 1
857   fi
858
859   if [[ ! -z "$NETSETS" && ! -f "$NETSETS" ]]; then
860     echo -e "${red}ERROR: ${NETSETS} does not exist! Exiting...${reset}"
861     exit 1
862   fi
863
864   if [[ ! -z "$INVENTORY_FILE" && ! -f "$INVENTORY_FILE" ]]; then
865     echo -e "{$red}ERROR: ${DEPLOY_SETTINGS_FILE} does not exist! Exiting...${reset}"
866     exit 1
867   fi
868
869   if [[ -z "$virtual" && -z "$INVENTORY_FILE" ]]; then
870     echo -e "${red}ERROR: You must specify an inventory file for baremetal deployments! Exiting...${reset}"
871     exit 1
872   fi
873 }
874
875 ##END FUNCTIONS
876
877 main() {
878   parse_cmdline "$@"
879   if [[ "$net_isolation_enabled" == "TRUE" ]]; then
880     echo -e "${blue}INFO: Parsing network settings file...${reset}"
881     parse_network_settings
882   fi
883   if ! configure_deps; then
884     echo -e "${red}Dependency Validation Failed, Exiting.${reset}"
885     exit 1
886   fi
887   if [ -n "$DEPLOY_SETTINGS_FILE" ]; then
888     parse_deploy_settings
889   fi
890   setup_instack_vm
891   if [ "$virtual" == "TRUE" ]; then
892     setup_virtual_baremetal
893   elif [ -n "$INVENTORY_FILE" ]; then
894     parse_inventory_file
895   fi
896   configure_undercloud
897   undercloud_prep_overcloud_deploy
898 }
899
900 main "$@"