Create local mirrors for offline installation
[fuel.git] / build / f_isoroot / f_bootstrap / bootstrap_admin_node.sh
1 #!/bin/bash
2 mkdir -p /var/log/puppet
3 exec > >(tee -i /var/log/puppet/bootstrap_admin_node.log)
4 exec 2>&1
5
6 FUEL_RELEASE=$(cat /etc/fuel_release)
7 ASTUTE_YAML='/etc/fuel/astute.yaml'
8 BOOTSTRAP_NODE_CONFIG="/etc/fuel/bootstrap_admin_node.conf"
9 bs_build_log='/var/log/fuel-bootstrap-image-build.log'
10 bs_status=0
11 # Backup network configs to this folder. Folder will be created only if
12 # backup process actually will be.
13 bup_folder="/var/bootstrap_admin_node_bup_$(date +%Y-%m-%d-%H-%M-%S)/"
14 ### Long messages inside code makes them more complicated to read...
15 # bootstrap messages
16 # FIXME fix help links
17 bs_skip_message="WARNING: Ubuntu bootstrap build has been skipped. \
18 Please build and activate bootstrap manually with CLI command \
19 \`fuel-bootstrap build --activate\`. \
20 While you don't activate any bootstrap - new nodes cannot be discovered \
21 and added to cluster. \
22 For more information please visit \
23 https://docs.mirantis.com/openstack/fuel/fuel-master/"
24 bs_error_message="WARNING: Failed to build the bootstrap image, see $bs_build_log \
25 for details. Perhaps your Internet connection is broken. Please fix the \
26 problem and run \`fuel-bootstrap build --activate\`. \
27 While you don\'t activate any bootstrap - new nodes cannot be discovered \
28 and added to cluster. \
29 For more information please visit \
30 https://docs.mirantis.com/openstack/fuel/fuel-master/"
31 bs_progress_message="There is no active bootstrap. Bootstrap image building \
32 is in progress. Usually it takes 15-20 minutes. It depends on your internet \
33 connection and hardware performance. Please reboot failed to discover nodes \
34 after bootstrap image become available."
35 bs_done_message="Default bootstrap image building done. Now you can boot new \
36 nodes over PXE, they will be discovered and become available for installing \
37 OpenStack on them"
38 bs_centos_message="WARNING: Deprecated Centos bootstrap has been chosen \
39 and activated. Now you can boot new nodes over PXE, they will be discovered \
40 and become available for installing OpenStack on them."
41 # Update issues messages
42 update_warn_message="There is an issue connecting to the Fuel update repository. \
43 Please fix your connection prior to applying any updates. \
44 Once the connection is fixed, we recommend reviewing and applying \
45 Maintenance Updates for this release of Mirantis OpenStack: \
46 https://docs.mirantis.com/openstack/fuel/fuel-${FUEL_RELEASE}/\
47 release-notes.html#maintenance-updates"
48 update_done_message="We recommend reviewing and applying Maintenance Updates \
49 for this release of Mirantis OpenStack: \
50 https://docs.mirantis.com/openstack/fuel/fuel-${FUEL_RELEASE}/\
51 release-notes.html#maintenance-updates"
52 fuelmenu_fail_message="Fuelmenu was not able to generate '/etc/fuel/astute.yaml' file! \
53 Please, restart it manualy using 'fuelmenu' command."
54
55 function countdown() {
56   local i
57   sleep 1
58   for ((i=$1-1; i>=1; i--)); do
59     printf '\b\b\b\b%04d' "$i"
60     sleep 1
61   done
62 }
63
64 function fail() {
65   echo "ERROR: Fuel node deployment FAILED! Check /var/log/puppet/bootstrap_admin_node.log for details" 1>&2
66   exit 1
67 }
68
69 function get_ethernet_interfaces() {
70   # Get list of all ethernet interfaces, non-virtual, not a wireless
71   for DEV in /sys/class/net/* ; do
72     # Take only links into account, skip files
73     if test ! -L $DEV ; then
74        continue
75     fi
76     DEVPATH=$(readlink -f $DEV)
77     # Avoid virtual devices like loopback, tunnels, bonding, vlans ...
78     case $DEVPATH in
79          */virtual/*)
80             continue
81          ;;
82     esac
83     IF=${DEVPATH##*/}
84     # Check ethernet only
85     case "`cat $DEV/type`" in
86          1)
87          # TYPE=1 is ethernet, may also be wireless, bond, tunnel ...
88          # Virtual lo, bound, vlan, tunneling has been skipped before
89          if test -d $DEV/wireless -o -L $DEV/phy80211 ;
90          then
91               continue
92          else
93          # Catch ethernet non-virtual device
94               echo $IF
95          fi
96          ;;
97          *) continue
98          ;;
99     esac
100   done
101 }
102
103 # Get value of a key from ifcfg-* files
104 # Usage:
105 #   get_ifcfg_value NAME /etc/sysconfig/network-scripts/ifcfg-eth0
106 function get_ifcfg_value {
107     local key=$1
108     local path=$2
109     local value=''
110     if [[ -f ${path} ]]; then
111         value=$(awk -F\= "\$1==\"${key}\" {print \$2}" ${path})
112         value=${value//\"/}
113     fi
114     echo ${value}
115 }
116
117 # Workaround to fix dracut network configuration approach:
118 #   Bring down all network interfaces which have the same IP
119 #   address statically configured as 'primary' interface
120 function ifdown_ethernet_interfaces {
121     local adminif_ipaddr
122     local if_config
123     local if_name
124     local if_ipaddr
125
126     adminif_ipaddr=$(get_ifcfg_value IPADDR /etc/sysconfig/network-scripts/ifcfg-${ADMIN_INTERFACE})
127     if [[ -z "${adminif_ipaddr}" ]]; then
128         return
129     fi
130     for if_config in $(find /etc/sysconfig/network-scripts -name 'ifcfg-*' ! -name 'ifcfg-lo'); do
131         if_name=$(get_ifcfg_value NAME $if_config)
132         if [[ "${if_name}" == "${ADMIN_INTERFACE}" ]]; then
133             continue
134         fi
135         if_ipaddr=$(get_ifcfg_value IPADDR $if_config)
136         if [[ "${if_ipaddr}" == "${adminif_ipaddr}" ]]; then
137             echo "Interface '${if_name}' uses the same ip '${if_ipaddr}' as admin interface '${ADMIN_INTERFACE}', removing ..."
138             ifdown ${if_name}
139             mkdir -p "${bup_folder}"
140             mv -f "${if_config}" "${bup_folder}"
141         fi
142     done
143 }
144
145 # Check if interface name is valid by checking that
146 # a config file with NAME equal to given name exists.
147 function ifname_valid {
148     local adminif_name=$1
149     local if_name
150     local if_config
151     for if_config in $(find /etc/sysconfig/network-scripts -name 'ifcfg-*' ! -name 'ifcfg-lo'); do
152         if_name=$(get_ifcfg_value NAME $if_config)
153         if [[ "${if_name}" == "${adminif_name}" ]]; then
154             return 0
155         fi
156     done
157     return 1
158 }
159
160
161 # LANG variable is a workaround for puppet-3.4.2 bug. See LP#1312758 for details
162 export LANG=en_US.UTF8
163 # Be sure, that network devices have been initialized
164 udevadm trigger --subsystem-match=net
165 udevadm settle
166
167 # Import bootstrap_admin_node.conf if exists
168 if [ -f "${BOOTSTRAP_NODE_CONFIG}" ]; then
169     source "${BOOTSTRAP_NODE_CONFIG}"
170 fi
171
172 # Set defaults to unset / empty variables
173 # Although eth0 is not always valid it's a good well-known default
174 # If there is no such interface it will fail to pass ifname_valid
175 # check and will be replaced.
176 OLD_ADMIN_INTERFACE=${ADMIN_INTERFACE}
177 ADMIN_INTERFACE=${ADMIN_INTERFACE:-'eth0'}
178 showmenu=${showmenu:-'no'}
179
180 # Now check that ADMIN_INTERFACE points to a valid interface
181 # If it doesn't fallback to getting the first interface name
182 # from a list of all available interfaces sorted alphabetically
183 if ! ifname_valid $ADMIN_INTERFACE; then
184     # Take the very first ethernet interface as an admin interface
185     ADMIN_INTERFACE=$(get_ethernet_interfaces | sort -V | head -1)
186 fi
187
188 if [[ "${OLD_ADMIN_INTERFACE}" != "${ADMIN_INTERFACE}" ]]; then
189   echo "Saving ADMIN_INTERFACE value"
190   sed -ie "s/^ADMIN_INTERFACE=.*/ADMIN_INTERFACE=${ADMIN_INTERFACE}/g" \
191     ${BOOTSTRAP_NODE_CONFIG}
192 fi
193
194 echo "Applying admin interface '$ADMIN_INTERFACE'"
195 export ADMIN_INTERFACE
196
197 echo "Bringing down ALL network interfaces except '${ADMIN_INTERFACE}'"
198 ifdown_ethernet_interfaces
199 systemctl restart network
200
201 echo "Applying default Fuel settings..."
202 set -x
203 fuelmenu --save-only --iface=$ADMIN_INTERFACE
204 set +x
205 echo "Done!"
206
207 ### OPNFV addition BEGIN
208 shopt -s nullglob
209 for script in /opt/opnfv/bootstrap/pre.d/*.sh
210 do
211   echo "Pre script: $script" >> /root/pre.log 2>&1
212   $script >> /root/pre.log 2>&1
213 done
214 shopt -u nullglob
215 ### OPNFV addition END
216
217 # Enable sshd
218 systemctl enable sshd
219 systemctl start sshd
220
221 if [[ "$showmenu" == "yes" || "$showmenu" == "YES" ]]; then
222   fuelmenu
223   else
224   #Give user 15 seconds to enter fuelmenu or else continue
225   echo
226   echo -n "Press a key to enter Fuel Setup (or press ESC to skip)...   15"
227   countdown 15 & pid=$!
228   if ! read -s -n 1 -t 15 key; then
229     echo -e "\nSkipping Fuel Setup..."
230   else
231     { kill "$pid"; wait $!; } 2>/dev/null
232     case "$key" in
233       $'\e')  echo "Skipping Fuel Setup.."
234               ;;
235       *)      echo -e "\nEntering Fuel Setup..."
236               fuelmenu
237               ;;
238     esac
239   fi
240 fi
241
242 if [ ! -f "${ASTUTE_YAML}" ]; then
243   echo ${fuelmenu_fail_message}
244   fail
245 fi
246
247 systemctl reload sshd
248
249 # Enable iptables
250 systemctl enable iptables.service
251 systemctl start iptables.service
252
253
254 if [ "$wait_for_external_config" == "yes" ]; then
255   wait_timeout=3000
256   pidfile=/var/lock/wait_for_external_config
257   echo -n "Waiting for external configuration (or press ESC to skip)...
258 $wait_timeout"
259   countdown $wait_timeout & countdown_pid=$!
260   exec -a wait_for_external_config sleep $wait_timeout & wait_pid=$!
261   echo $wait_pid > $pidfile
262   while ps -p $countdown_pid &> /dev/null && ps -p $wait_pid &>/dev/null; do
263     read -s -n 1 -t 2 key
264     case "$key" in
265       $'\e')   echo -e "\b\b\b\b abort on user input"
266                break
267                ;;
268       *)       ;;
269     esac
270   done
271   { kill $countdown_pid $wait_pid & wait $!; }
272   rm -f $pidfile
273 fi
274
275
276 #Reread /etc/sysconfig/network to inform puppet of changes
277 . /etc/sysconfig/network
278 hostname "$HOSTNAME"
279
280 # XXX: ssh keys which should be included into the bootstrap image are
281 # generated during containers deployment. However cobbler checkfs for
282 # a kernel and initramfs when creating a profile, which poses chicken
283 # and egg problem. Fortunately cobbler is pretty happy with empty files
284 # so it's easy to break the loop.
285 make_ubuntu_bootstrap_stub () {
286         local bootstrap_dir='/var/www/nailgun/bootstraps/active_bootstrap'
287         local bootstrap_stub_dir='/var/www/nailgun/bootstraps/bootstrap_stub'
288         mkdir -p ${bootstrap_stub_dir}
289         for item in vmlinuz initrd.img; do
290                 touch "${bootstrap_stub_dir}/$item"
291         done
292         ln -s ${bootstrap_stub_dir} ${bootstrap_dir} || true
293 }
294
295 get_bootstrap_flavor () {
296         python <<-EOF
297         from yaml import safe_load
298         with open("$ASTUTE_YAML", 'r') as f:
299             conf = safe_load(f).get('BOOTSTRAP', {})
300         print(conf.get('flavor', 'centos').lower())
301         EOF
302 }
303
304 get_bootstrap_skip () {
305         python <<-EOF
306         from yaml import safe_load
307         with open("$ASTUTE_YAML", 'r') as f:
308             conf = safe_load(f).get('BOOTSTRAP', {})
309         print(conf.get('skip_default_img_build', False))
310         EOF
311 }
312
313 set_ui_bootstrap_error () {
314         # This notify can't be closed or removed by user.
315         # For remove notify - send empty string.
316         local message=$1
317         python <<-EOF
318         from fuel_bootstrap.utils import notifier
319         notifier.notify_webui('${message}')
320         EOF
321 }
322
323 # Actually build the bootstrap image
324 build_ubuntu_bootstrap () {
325         local ret=1
326         echo ${bs_progress_message} >&2
327         set_ui_bootstrap_error "${bs_progress_message}" >&2
328         if fuel-bootstrap -v --debug build --activate >>"$bs_build_log" 2>&1; then
329           ret=0
330           fuel notify --topic "done" --send "${bs_done_message}"
331         else
332           ret=1
333           set_ui_bootstrap_error "${bs_error_message}" >&2
334         fi
335         # perform hard-return from func
336         # this part will update input $1 variable
337         local  __resultvar=$1
338         eval $__resultvar="'${ret}'"
339         return $ret
340 }
341
342 # Create empty files to make cobbler happy
343 # (even if we don't use Ubuntu based bootstrap)
344 make_ubuntu_bootstrap_stub
345
346 service docker start
347
348 old_sysctl_vm_value=$(sysctl -n vm.min_free_kbytes)
349 if [ ${old_sysctl_vm_value} -lt 65535 ]; then
350   echo "Set vm.min_free_kbytes..."
351   sysctl -w vm.min_free_kbytes=65535
352 fi
353
354 if [ -f /root/.build_images ]; then
355   #Fail on all errors
356   set -e
357   trap fail EXIT
358
359   echo "Loading Fuel base image for Docker..."
360   docker load -i /var/www/nailgun/docker/images/fuel-images.tar
361
362   echo "Building Fuel Docker images..."
363   WORKDIR=$(mktemp -d /tmp/docker-buildXXX)
364   SOURCE=/var/www/nailgun/docker
365   REPO_CONT_ID=$(docker -D run -d -p 80 -v /var/www/nailgun:/var/www/nailgun fuel/centos sh -c 'mkdir -p /var/www/html/repo/os;ln -sf /var/www/nailgun/centos/x86_64 /var/www/html/repo/os/x86_64;ln -s /var/www/nailgun/mos-centos/x86_64 /var/www/html/mos-repo;/usr/sbin/apachectl -DFOREGROUND')
366   RANDOM_PORT=$(docker port $REPO_CONT_ID 80 | cut -d':' -f2)
367
368   for imagesource in /var/www/nailgun/docker/sources/*; do
369     if ! [ -f "$imagesource/Dockerfile" ]; then
370       echo "Skipping ${imagesource}..."
371       continue
372     fi
373     image=$(basename "$imagesource")
374     cp -R "$imagesource" $WORKDIR/$image
375     mkdir -p $WORKDIR/$image/etc
376     cp -R /etc/puppet /etc/fuel $WORKDIR/$image/etc
377     sed -e "s/_PORT_/${RANDOM_PORT}/" -i $WORKDIR/$image/Dockerfile
378     sed -r -e 's/^"?PRODUCTION"?:.*/PRODUCTION: "docker-build"/' -i $WORKDIR/$image/etc/fuel/astute.yaml
379     # FIXME(kozhukalov): Once this patch https://review.openstack.org/#/c/219581/ is merged
380     # remove this line. fuel-library is to use PRODUCTION value from astute.yaml instead of
381     # the same value from version.yaml. It is a part of version.yaml deprecation plan.
382     sed -e 's/production:.*/production: "docker-build"/' -i $WORKDIR/$image/etc/fuel/version.yaml
383     docker build -t fuel/${image}_${FUEL_RELEASE} $WORKDIR/$image
384   done
385   docker rm -f $REPO_CONT_ID
386   rm -rf "$WORKDIR"
387
388   #Remove trap for normal deployment
389   trap - EXIT
390   set +e
391 else
392   echo "Loading docker images. (This may take a while)"
393   docker load -i /var/www/nailgun/docker/images/fuel-images.tar
394 fi
395
396 if [ ${old_sysctl_vm_value} -lt 65535 ]; then
397   echo "Restore sysctl vm.min_free_kbytes value..."
398   sysctl -w vm.min_free_kbytes=${old_sysctl_vm_value}
399 fi
400
401 # apply puppet
402 puppet apply --detailed-exitcodes -d -v /etc/puppet/modules/nailgun/examples/host-only.pp
403 if [ $? -ge 4 ];then
404   fail
405 fi
406
407 # Sync time
408 systemctl stop ntpd
409 systemctl start ntpdate || echo "Failed to synchronize time with 'ntpdate'"
410 systemctl start ntpd
411
412 rmdir /var/log/remote && ln -s /var/log/docker-logs/remote /var/log/remote
413
414 dockerctl check || fail
415 bash /etc/rc.local
416
417 ### OPNFV addition BEGIN
418 shopt -s nullglob
419 for script in /opt/opnfv/bootstrap/post.d/*.sh
420 do
421   echo "Post script: $script" >> /root/post.log 2>&1
422   $script >> /root/post.log 2>&1
423 done
424 shopt -u nullglob
425 ### OPNFV addition END
426
427 if [ "`get_bootstrap_flavor`" = "ubuntu" ]; then
428   if [ "`get_bootstrap_skip`" = "False" ]; then
429     build_ubuntu_bootstrap bs_status || true
430   else
431     fuel notify --topic "warning" --send "${bs_skip_message}"
432     bs_status=2
433   fi
434 else
435   fuel notify --topic "warning" --send "${bs_centos_message}"
436   bs_status=3
437 fi
438
439
440 # Enable updates repository
441 cat > /etc/yum.repos.d/mos${FUEL_RELEASE}-updates.repo << EOF
442 [mos${FUEL_RELEASE}-updates]
443 name=mos${FUEL_RELEASE}-updates
444 baseurl=http://mirror.fuel-infra.org/mos-repos/centos/mos${FUEL_RELEASE}-centos\$releasever-fuel/updates/x86_64/
445 gpgcheck=0
446 skip_if_unavailable=1
447 EOF
448
449 # Enable security repository
450 cat > /etc/yum.repos.d/mos${FUEL_RELEASE}-security.repo << EOF
451 [mos${FUEL_RELEASE}-security]
452 name=mos${FUEL_RELEASE}-security
453 baseurl=http://mirror.fuel-infra.org/mos-repos/centos/mos${FUEL_RELEASE}-centos\$releasever-fuel/security/x86_64/
454 gpgcheck=0
455 skip_if_unavailable=1
456 EOF
457
458 #Check if repo is accessible
459 echo "Checking for access to updates repository..."
460 repourl=$(yum repolist all -v | awk '{if ($1 ~ "baseurl" && $3 ~ "updates") print $3}' | head -1)
461 if urlaccesscheck check "$repourl" ; then
462   UPDATE_ISSUES=0
463 else
464   UPDATE_ISSUES=1
465 fi
466
467 if [ $UPDATE_ISSUES -eq 1 ]; then
468   message=${update_warn_message}
469   level="warning"
470 else
471   message=${update_done_message}
472   level="done"
473 fi
474 echo
475 echo "*************************************************"
476 echo -e "${message}"
477 echo "*************************************************"
478 fuel notify --topic "${level}" --send $(echo "${message}" | tr '\r\n' ' ') 2>&1
479
480 # Perform bootstrap messaging to stdout
481 case ${bs_status} in
482   1)
483   echo -e "${bs_error_message}"
484   echo "*************************************************"
485   ;;
486   2)
487   echo -e "${bs_skip_message}"
488   echo "*************************************************"
489   ;;
490   3)
491   echo -e "${bs_centos_message}"
492   echo "*************************************************"
493   ;;
494 esac
495
496 echo "Fuel node deployment complete!"
497 # Sleep for agetty autologon
498 sleep 3