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