Merge "base image: Explicitly call kpartx"
[fuel.git] / ci / deploy.sh
1 #!/bin/bash -e
2 # shellcheck disable=SC2034,SC2154,SC1090,SC1091
3 ##############################################################################
4 # Copyright (c) 2017 Ericsson AB, Mirantis Inc., Enea AB and others.
5 # jonas.bjurel@ericsson.com
6 # All rights reserved. This program and the accompanying materials
7 # are made available under the terms of the Apache License, Version 2.0
8 # which accompanies this distribution, and is available at
9 # http://www.apache.org/licenses/LICENSE-2.0
10 ##############################################################################
11
12 ##############################################################################
13 # BEGIN of Exit handlers
14 #
15 do_exit () {
16     local RC=$?
17     clean
18     cleanup_mounts
19     if [ ${RC} -eq 0 ]; then
20         notify "\n[OK] MCP: Openstack installation finished succesfully!\n\n" 2
21     else
22         notify "\n[ERROR] MCP: Openstack installation threw a fatal error!\n\n"
23     fi
24 }
25 #
26 # End of Exit handlers
27 ##############################################################################
28
29 ##############################################################################
30 # BEGIN of usage description
31 #
32 usage ()
33 {
34 cat << EOF
35 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
36 $(notify "$(basename "$0"): Deploy the Fuel@OPNFV MCP stack" 3)
37
38 $(notify "USAGE:" 2)
39   $(basename "$0") -b base-uri -l lab-name -p pod-name -s deploy-scenario \\
40     [-B PXE Bridge [-B Mgmt Bridge [-B Internal Bridge [-B Public Bridge]]]] \\
41     [-S storage-dir] [-L /path/to/log/file.tar.gz] \\
42     [-f[f]] [-F] [-e | -E[E]] [-d] [-D]
43
44 $(notify "OPTIONS:" 2)
45   -b  Base-uri for the stack-configuration structure
46   -B  Bridge(s): 1st usage = PXE, 2nd = Mgmt, 3rd = Internal, 4th = Public
47   -d  Dry-run
48   -D  Debug logging
49   -e  Do not launch environment deployment
50   -E  Remove existing VCP VMs (use twice to redeploy baremetal nodes)
51   -f  Deploy on existing Salt master (use twice to also skip config sync)
52   -F  Do only create a Salt master
53   -h  Print this message and exit
54   -l  Lab-name
55   -p  Pod-name
56   -s  Deploy-scenario short-name
57   -S  Storage dir for VM images
58   -L  Deployment log path and file name
59
60 $(notify "Description:" 2)
61 Deploys the Fuel@OPNFV stack on the indicated lab resource.
62
63 This script provides the Fuel@OPNFV deployment abstraction.
64 It depends on the OPNFV official configuration directory/file structure
65 and provides a fairly simple mechanism to execute a deployment.
66
67 $(notify "Input parameters to the build script are:" 2)
68 -b Base URI to the configuration directory (needs to be provided in URI style,
69    it can be a local resource: file:// or a remote resource http(s)://).
70    A POD Descriptor File (PDF) should be available at:
71    <base-uri>/labs/<lab-name>/<pod-name>.yaml
72    The default is './mcp/config'.
73 -B Bridges to be used by deploy script. It can be specified several times,
74    or as a comma separated list of bridges, or both: -B br1 -B br2,br3
75    First occurence sets PXE Brige, next Mgmt, then Internal and Public.
76    For an empty value, the deploy script will use virsh to create the default
77    expected network (e.g. -B pxe,,,public will use existing "pxe" and "public"
78    bridges, respectively create "mgmt" and "internal").
79    Note that a virtual network "mcpcontrol" is always created. For virtual
80    deploys, "mcpcontrol" is also used for PXE, leaving the PXE bridge unused.
81    For baremetal deploys, PXE bridge is used for baremetal node provisioning,
82    while "mcpcontrol" is used to provision the infrastructure VMs only.
83    The default is 'pxebr'.
84 -d Dry-run - Produce deploy config files, but do not execute deploy
85 -D Debug logging - Enable extra logging in sh deploy scripts (set -x)
86 -e Do not launch environment deployment
87 -E Remove existing VCP VMs. It will destroy and undefine all VCP VMs
88    currently defined on cluster KVM nodes. If specified twice (e.g. -E -E),
89    baremetal nodes (VCP too, implicitly) will be removed, then reprovisioned.
90    Only applicable for baremetal deploys.
91 -f Deploy on existing Salt master. It will skip infrastructure VM creation,
92    but it will still sync reclass configuration from current repo to Salt
93    Master node. If specified twice (e.g. -f -f), config sync will also be
94    skipped.
95 -F Do only create a Salt master
96 -h Print this message and exit
97 -L Deployment log path and name, eg. -L /home/jenkins/job.log.tar.gz
98 -l Lab name as defined in the configuration directory, e.g. lf
99 -p POD name as defined in the configuration directory, e.g. pod2
100 -s Deployment-scenario, this points to a short deployment scenario name, which
101    has to be defined in config directory (e.g. os-odl-nofeature-ha).
102 -S Storage dir for VM images, default is mcp/deploy/images
103
104 $(notify "[NOTE] sudo & virsh priviledges are needed for this script to run" 3)
105
106 Example:
107
108 $(notify "sudo $(basename "$0") \\
109   -b file:///home/jenkins/securedlab \\
110   -l lf -p pod2 \\
111   -s os-odl-nofeature-ha" 2)
112 EOF
113 }
114
115 #
116 # END of usage description
117 ##############################################################################
118
119 ##############################################################################
120 # BEGIN of colored notification wrapper
121 #
122 notify() {
123     tput setaf "${2:-1}" || true
124     echo -en "${1:-"[WARN] Unsupported opt arg: $3\\n"}"
125     tput sgr0
126 }
127 #
128 # END of colored notification wrapper
129 ##############################################################################
130
131 ##############################################################################
132 # BEGIN of deployment clean-up
133 #
134 clean() {
135     echo "Cleaning up deploy tmp directories"
136 }
137 #
138 # END of deployment clean-up
139 ##############################################################################
140
141 ##############################################################################
142 # BEGIN of variables to customize
143 #
144 CI_DEBUG=${CI_DEBUG:-0}; [[ "${CI_DEBUG}" =~ (false|0) ]] || set -x
145 REPO_ROOT_PATH=$(readlink -f "$(dirname "${BASH_SOURCE[0]}")/..")
146 DEPLOY_DIR=$(cd "${REPO_ROOT_PATH}/mcp/scripts"; pwd)
147 STORAGE_DIR=$(cd "${REPO_ROOT_PATH}/mcp/deploy/images"; pwd)
148 RECLASS_CLUSTER_DIR=$(cd "${REPO_ROOT_PATH}/mcp/reclass/classes/cluster"; pwd)
149 DEPLOY_TYPE='baremetal'
150 OPNFV_BRIDGES=('pxebr' 'mgmt' 'internal' 'public')
151 URI_REGEXP='(file|https?|ftp)://.*'
152 BASE_CONFIG_URI="file://${REPO_ROOT_PATH}/mcp/config"
153
154 # Customize deploy workflow
155 DRY_RUN=${DRY_RUN:-0}
156 USE_EXISTING_INFRA=${USE_EXISTING_INFRA:-0}
157 INFRA_CREATION_ONLY=${INFRA_CREATION_ONLY:-0}
158 NO_DEPLOY_ENVIRONMENT=${NO_DEPLOY_ENVIRONMENT:-0}
159 ERASE_ENV=${ERASE_ENV:-0}
160
161 source "${DEPLOY_DIR}/globals.sh"
162 source "${DEPLOY_DIR}/lib.sh"
163
164 #
165 # END of variables to customize
166 ##############################################################################
167
168 ##############################################################################
169 # BEGIN of main
170 #
171 set +x
172 OPNFV_BRIDGE_IDX=0
173 while getopts "b:B:dDfEFl:L:p:s:S:he" OPTION
174 do
175     case $OPTION in
176         b)
177             BASE_CONFIG_URI=${OPTARG}
178             if [[ ! $BASE_CONFIG_URI =~ ${URI_REGEXP} ]]; then
179                 notify "[ERROR] -b $BASE_CONFIG_URI - invalid URI\n"
180                 usage
181                 exit 1
182             fi
183             ;;
184         B)
185             OIFS=${IFS}
186             IFS=','
187             OPT_BRIDGES=($OPTARG)
188             for bridge in "${OPT_BRIDGES[@]}"; do
189                 if [ -n "${bridge}" ]; then
190                     OPNFV_BRIDGES[${OPNFV_BRIDGE_IDX}]="${bridge}"
191                 fi
192                 ((OPNFV_BRIDGE_IDX+=1))
193             done
194             IFS=${OIFS}
195             ;;
196         d)
197             DRY_RUN=1
198             ;;
199         D)
200             CI_DEBUG=1
201             ;;
202         f)
203             ((USE_EXISTING_INFRA+=1))
204             ;;
205         F)
206             INFRA_CREATION_ONLY=1
207             ;;
208         e)
209             NO_DEPLOY_ENVIRONMENT=1
210             ;;
211         E)
212             ((ERASE_ENV+=1))
213             ;;
214         l)
215             TARGET_LAB=${OPTARG}
216             ;;
217         L)
218             DEPLOY_LOG="${OPTARG}"
219             ;;
220         p)
221             TARGET_POD=${OPTARG}
222             if [[ "${TARGET_POD}" =~ "virtual" ]]; then
223                 DEPLOY_TYPE='virtual'
224             fi
225             ;;
226         s)
227             DEPLOY_SCENARIO=${OPTARG}
228             ;;
229         S)
230             if [[ ${OPTARG} ]]; then
231                 STORAGE_DIR="${OPTARG}"
232             fi
233             ;;
234         h)
235             usage
236             exit 0
237             ;;
238         *)
239             notify "[ERROR] Arguments not according to new argument style\n"
240             exit 1
241             ;;
242     esac
243 done
244
245 if [[ "$(sudo whoami)" != 'root' ]]; then
246     notify "[ERROR] This script requires sudo rights\n" 1>&2
247     exit 1
248 fi
249
250 # Validate mandatory arguments are set
251 if [ -z "${TARGET_LAB}" ] || [ -z "${TARGET_POD}" ] || \
252    [ -z "${DEPLOY_SCENARIO}" ]; then
253     notify "[ERROR] At least one of the mandatory args is missing!\n" 1>&2
254     usage
255     exit 1
256 fi
257
258 [[ "${CI_DEBUG}" =~ (false|0) ]] || set -x
259
260 # Enable the automatic exit trap
261 trap do_exit SIGINT SIGTERM EXIT
262
263 # Set no restrictive umask so that Jenkins can remove any residuals
264 umask 0000
265
266 clean
267
268 pushd "${DEPLOY_DIR}" > /dev/null
269 # Prepare the deploy config files based on lab/pod information, deployment
270 # scenario, etc.
271
272 # Install required packages
273 [ -n "$(command -v apt-get)" ] && sudo apt-get install -y \
274   git make rsync mkisofs curl virtinst cpu-checker qemu-kvm uuid-runtime \
275   libvirt-bin cloud-guest-utils e2fsprogs kpartx
276 [ -n "$(command -v yum)" ] && sudo yum install -y --skip-broken \
277   git make rsync genisoimage curl virt-install qemu-kvm util-linux \
278   libvirt cloud-utils-growpart e2fsprogs kpartx
279
280 # For baremetal, python is indirectly required for PDF parsing
281 if [ "${DEPLOY_TYPE}" = 'baremetal' ]; then
282   [ -n "$(command -v apt-get)" ] && sudo apt-get install -y \
283     python python-ipaddress python-jinja2 python-yaml
284   [ -n "$(command -v yum)" ] && sudo yum install -y --skip-broken \
285     python python-ipaddress python-jinja2 python-yaml
286 fi
287
288 # AArch64 VMs use AAVMF (guest UEFI)
289 if [ "$(uname -m)" = 'aarch64' ]; then
290   [ -n "$(command -v apt-get)" ] && sudo apt-get install -y qemu-efi
291   [ -n "$(command -v yum)" ] && sudo yum install -y --skip-broken AAVMF
292 fi
293
294 if ! virsh list >/dev/null 2>&1; then
295     notify "[ERROR] This script requires hypervisor access\n" 1>&2
296     exit 1
297 fi
298
299 # Clone git submodules and apply our patches
300 make -C "${REPO_ROOT_PATH}/mcp/patches" deepclean patches-import
301
302 # Convert Pharos-compatible POD Descriptor File (PDF) to reclass model input
303 PHAROS_GEN_CONFIG_SCRIPT="./pharos/config/utils/generate_config.py"
304 PHAROS_INSTALLER_ADAPTER="./pharos/config/installers/fuel/pod_config.yml.j2"
305 BASE_CONFIG_PDF="${BASE_CONFIG_URI}/labs/${TARGET_LAB}/${TARGET_POD}.yaml"
306 BASE_CONFIG_IDF="${BASE_CONFIG_URI}/labs/${TARGET_LAB}/idf-${TARGET_POD}.yaml"
307 LOCAL_PDF="${STORAGE_DIR}/$(basename "${BASE_CONFIG_PDF}")"
308 LOCAL_IDF="${STORAGE_DIR}/$(basename "${BASE_CONFIG_IDF}")"
309 LOCAL_PDF_RECLASS="${STORAGE_DIR}/pod_config.yml"
310 if ! curl --create-dirs -o "${LOCAL_PDF}" "${BASE_CONFIG_PDF}"; then
311     if [ "${DEPLOY_TYPE}" = 'baremetal' ]; then
312         notify "[ERROR] Could not retrieve PDF (Pod Descriptor File)!\n" 1>&2
313         exit 1
314     else
315         notify "[WARN] Could not retrieve PDF (Pod Descriptor File)!\n" 3
316     fi
317 elif ! curl -o "${LOCAL_IDF}" "${BASE_CONFIG_IDF}"; then
318     notify "[WARN] POD has no IDF (Installer Descriptor File)!\n" 3
319 elif ! "${PHAROS_GEN_CONFIG_SCRIPT}" -y "${LOCAL_PDF}" \
320     -j "${PHAROS_INSTALLER_ADAPTER}" > "${LOCAL_PDF_RECLASS}"; then
321     notify "[ERROR] Could not convert PDF to reclass model input!\n" 1>&2
322     exit 1
323 fi
324
325 # Check scenario file existence
326 SCENARIO_DIR="../config/scenario"
327 if [ ! -f  "${SCENARIO_DIR}/${DEPLOY_TYPE}/${DEPLOY_SCENARIO}.yaml" ]; then
328     notify "[WARN] ${DEPLOY_SCENARIO}.yaml not found!\n" 3
329     notify "[WARN] Setting simplest scenario (os-nosdn-nofeature-noha)\n" 3
330     DEPLOY_SCENARIO='os-nosdn-nofeature-noha'
331     if [ ! -f  "${SCENARIO_DIR}/${DEPLOY_TYPE}/${DEPLOY_SCENARIO}.yaml" ]; then
332         notify "[ERROR] Scenario definition file is missing!\n" 1>&2
333         exit 1
334     fi
335 fi
336
337 # Check defaults file existence
338 if [ ! -f  "${SCENARIO_DIR}/defaults-$(uname -i).yaml" ]; then
339     notify "[ERROR] Scenario defaults file is missing!\n" 1>&2
340     exit 1
341 fi
342
343 # Get required infra deployment data
344 set +x
345 eval "$(parse_yaml "${SCENARIO_DIR}/defaults-$(uname -i).yaml")"
346 eval "$(parse_yaml "${SCENARIO_DIR}/${DEPLOY_TYPE}/${DEPLOY_SCENARIO}.yaml")"
347 eval "$(parse_yaml "${LOCAL_PDF_RECLASS}")"
348 [[ "${CI_DEBUG}" =~ (false|0) ]] || set -x
349
350 export CLUSTER_DOMAIN=${cluster_domain}
351
352 # Serialize vnode data as '<name0>,<ram0>,<vcpu0>|<name1>,<ram1>,<vcpu1>[...]'
353 for node in "${virtual_nodes[@]}"; do
354     virtual_custom_ram="virtual_${node}_ram"
355     virtual_custom_vcpus="virtual_${node}_vcpus"
356     virtual_nodes_data+="${node},"
357     virtual_nodes_data+="${!virtual_custom_ram:-$virtual_default_ram},"
358     virtual_nodes_data+="${!virtual_custom_vcpus:-$virtual_default_vcpus}|"
359 done
360 virtual_nodes_data=${virtual_nodes_data%|}
361
362 # Serialize repos, packages to (pre-)install/remove for:
363 # - foundation node VM base image (virtual: all VMs, baremetal: cfg01|mas01)
364 # - virtualized control plane VM base image (only when VCP is used)
365 base_image_flavors=common
366 if [[ "${cluster_states[*]}" =~ virtual_control ]]; then
367   base_image_flavors+=" control"
368 fi
369 for sc in ${base_image_flavors}; do
370   for va in apt_keys apt_repos pkg_install pkg_remove; do
371     key=virtual_${sc}_${va}
372     eval "${key}=\${${key}[@]// /|}"
373     eval "${key}=\${${key}// /,}"
374     virtual_repos_pkgs+="${!key}^"
375   done
376 done
377 virtual_repos_pkgs=${virtual_repos_pkgs%^}
378
379 # Expand reclass and virsh network templates
380 for tp in "${RECLASS_CLUSTER_DIR}/all-mcp-arch-common/opnfv/"*.template \
381     net_*.template; do
382         eval "cat <<-EOF
383                 $(<"${tp}")
384                 EOF" 2> /dev/null > "${tp%.template}"
385 done
386
387 # Convert Pharos-compatible PDF to reclass network definitions
388 if [ "${DEPLOY_TYPE}" = 'baremetal' ]; then
389     find "${RECLASS_CLUSTER_DIR}" -name '*.j2' | while read -r tp
390     do
391         if ! "${PHAROS_GEN_CONFIG_SCRIPT}" -y "${LOCAL_PDF}" \
392           -j "${tp}" > "${tp%.j2}"; then
393              notify "[ERROR] Could not convert PDF to reclass network defs!\n"
394              exit 1
395         fi
396     done
397 fi
398
399 # Map PDF networks 'admin', 'mgmt', 'private' and 'public' to bridge names
400 BR_NAMES=('admin' 'mgmt' 'private' 'public')
401 BR_NETS=( \
402     "${parameters__param_opnfv_maas_pxe_address}" \
403     "${parameters__param_opnfv_infra_config_address}" \
404     "${parameters__param_opnfv_openstack_compute_node01_tenant_address}" \
405     "${parameters__param_opnfv_openstack_compute_node01_external_address}" \
406 )
407 for ((i = 0; i < ${#BR_NETS[@]}; i++)); do
408     br_jump=$(eval echo "\$parameters__param_opnfv_jump_bridge_${BR_NAMES[i]}")
409     if [ -n "${br_jump}" ] && [ "${br_jump}" != 'None' ] && \
410        [ -d "/sys/class/net/${br_jump}/bridge" ]; then
411             notify "[OK] Bridge found for '${BR_NAMES[i]}': ${br_jump}\n" 2
412             OPNFV_BRIDGES[${i}]="${br_jump}"
413     elif [ -n "${BR_NETS[i]}" ]; then
414         bridge=$(ip addr | awk "/${BR_NETS[i]%.*}./ {print \$NF; exit}")
415         if [ -n "${bridge}" ] && [ -d "/sys/class/net/${bridge}/bridge" ]; then
416             notify "[OK] Bridge found for net ${BR_NETS[i]%.*}.0: ${bridge}\n" 2
417             OPNFV_BRIDGES[${i}]="${bridge}"
418         fi
419     fi
420 done
421 notify "[NOTE] Using bridges: ${OPNFV_BRIDGES[*]}\n" 2
422
423 # Infra setup
424 if [ ${DRY_RUN} -eq 1 ]; then
425     notify "[NOTE] Dry run, skipping all deployment tasks\n" 2 1>&2
426     exit 0
427 elif [ ${USE_EXISTING_INFRA} -gt 0 ]; then
428     notify "[NOTE] Use existing infra\n" 2 1>&2
429     check_connection
430 else
431     generate_ssh_key
432     prepare_vms "${base_image}" "${STORAGE_DIR}" "${virtual_repos_pkgs}" \
433       "${virtual_nodes[@]}"
434     create_networks "${OPNFV_BRIDGES[@]}"
435     create_vms "${STORAGE_DIR}" "${virtual_nodes_data}" "${OPNFV_BRIDGES[@]}"
436     update_mcpcontrol_network
437     start_vms "${virtual_nodes[@]}"
438     check_connection
439 fi
440 if [ ${USE_EXISTING_INFRA} -lt 2 ]; then
441     wait_for 5 "./salt.sh ${LOCAL_PDF_RECLASS}"
442 fi
443
444 # Openstack cluster setup
445 set +x
446 if [ ${INFRA_CREATION_ONLY} -eq 1 ] || [ ${NO_DEPLOY_ENVIRONMENT} -eq 1 ]; then
447     notify "[NOTE] Skip openstack cluster setup\n" 2
448 else
449     for state in "${cluster_states[@]}"; do
450         notify "[STATE] Applying state: ${state}\n" 2
451         # shellcheck disable=SC2086,2029
452         wait_for 5 "ssh ${SSH_OPTS} ${SSH_SALT} sudo \
453             CI_DEBUG=$CI_DEBUG ERASE_ENV=$ERASE_ENV \
454             /root/fuel/mcp/config/states/${state}"
455     done
456 fi
457
458 ./log.sh "${DEPLOY_LOG}"
459
460 popd > /dev/null
461
462 #
463 # END of main
464 ##############################################################################