| Anthony Young | b62b4ca | 2011-10-26 22:29:08 -0700 | [diff] [blame] | 1 | #!/bin/bash | 
|  | 2 | # | 
|  | 3 | # Copyright (c) 2011 Citrix Systems, Inc. | 
|  | 4 | # Copyright 2011 OpenStack LLC. | 
| Anthony Young | b62b4ca | 2011-10-26 22:29:08 -0700 | [diff] [blame] | 5 | # All Rights Reserved. | 
|  | 6 | # | 
|  | 7 | #    Licensed under the Apache License, Version 2.0 (the "License"); you may | 
|  | 8 | #    not use this file except in compliance with the License. You may obtain | 
|  | 9 | #    a copy of the License at | 
|  | 10 | # | 
|  | 11 | #         http://www.apache.org/licenses/LICENSE-2.0 | 
|  | 12 | # | 
|  | 13 | #    Unless required by applicable law or agreed to in writing, software | 
|  | 14 | #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | 
|  | 15 | #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | 
|  | 16 | #    License for the specific language governing permissions and limitations | 
|  | 17 | #    under the License. | 
|  | 18 | # | 
|  | 19 |  | 
|  | 20 | set -eux | 
|  | 21 |  | 
|  | 22 | . /etc/xensource-inventory | 
|  | 23 |  | 
|  | 24 | NAME="XenServer OpenStack VPX" | 
|  | 25 | DATA_VDI_SIZE="500MiB" | 
|  | 26 | BRIDGE_M= | 
|  | 27 | BRIDGE_P= | 
|  | 28 | KERNEL_PARAMS= | 
|  | 29 | VPX_FILE=os-vpx.xva | 
|  | 30 | AS_TEMPLATE= | 
|  | 31 | FROM_TEMPLATE= | 
|  | 32 | RAM= | 
|  | 33 | WAIT_FOR_NETWORK= | 
|  | 34 | BALLOONING= | 
|  | 35 |  | 
|  | 36 | usage() | 
|  | 37 | { | 
|  | 38 | cat << EOF | 
|  | 39 |  | 
|  | 40 | Usage: $0 [-f FILE_PATH] [-d DISK_SIZE] [-v BRIDGE_NAME] [-m BRIDGE_NAME] [-p BRIDGE_NAME] | 
|  | 41 | [-k PARAMS] [-r RAM] [-i|-c] [-w] [-b] | 
|  | 42 |  | 
|  | 43 | Installs XenServer OpenStack VPX. | 
|  | 44 |  | 
|  | 45 | OPTIONS: | 
|  | 46 |  | 
|  | 47 | -h           Shows this message. | 
|  | 48 | -i           Install OpenStack VPX as template. | 
|  | 49 | -c           Clone from existing template. | 
|  | 50 | -w           Wait for the network settings to show up before exiting. | 
|  | 51 | -b           Enable memory ballooning. When set min_RAM=RAM/2 max_RAM=RAM. | 
|  | 52 | -f path      Specifies the path to the XVA. | 
|  | 53 | Default to ./os-vpx.xva. | 
|  | 54 | -d disk-size Specifies the size in MiB for the data disk. | 
|  | 55 | Defaults to 500 MiB. | 
|  | 56 | -m bridge    Specifies the bridge for the isolated management network. | 
|  | 57 | Defaults to xenbr0. | 
|  | 58 | -v bridge    Specifies the bridge for the vm network | 
|  | 59 | -p bridge    Specifies the bridge for the externally facing network. | 
|  | 60 | -k params    Specifies kernel parameters. | 
|  | 61 | -r MiB       Specifies RAM used by the VPX, in MiB. | 
|  | 62 | By default it will take the value from the XVA. | 
|  | 63 |  | 
|  | 64 | EXAMPLES: | 
|  | 65 |  | 
|  | 66 | Create a VPX that connects to the isolated management network using the | 
|  | 67 | default bridge with a data disk of 1GiB: | 
|  | 68 | install-os-vpx.sh -f /root/os-vpx-devel.xva -d 1024 | 
|  | 69 |  | 
|  | 70 | Create a VPX that connects to the isolated management network using xenbr1 | 
|  | 71 | as bridge: | 
|  | 72 | install-os-vpx.sh -m xenbr1 | 
|  | 73 |  | 
|  | 74 | Create a VPX that connects to both the management and public networks | 
|  | 75 | using xenbr1 and xapi4 as bridges: | 
|  | 76 | install-os-vpx.sh -m xenbr1 -p xapi4 | 
|  | 77 |  | 
|  | 78 | Create a VPX that connects to both the management and public networks | 
|  | 79 | using the default for management traffic: | 
|  | 80 | install-os-vpx.sh -m xapi4 | 
|  | 81 |  | 
|  | 82 | Create a VPX that automatically becomes the master: | 
|  | 83 | install-os-vpx.sh -k geppetto_master=true | 
|  | 84 |  | 
|  | 85 | EOF | 
|  | 86 | } | 
|  | 87 |  | 
|  | 88 | get_params() | 
|  | 89 | { | 
|  | 90 | while getopts "hicwbf:d:v:m:p:k:r:" OPTION; | 
|  | 91 | do | 
|  | 92 | case $OPTION in | 
|  | 93 | h) usage | 
|  | 94 | exit 1 | 
|  | 95 | ;; | 
|  | 96 | i) | 
|  | 97 | AS_TEMPLATE=1 | 
|  | 98 | ;; | 
|  | 99 | c) | 
|  | 100 | FROM_TEMPLATE=1 | 
|  | 101 | ;; | 
|  | 102 | w) | 
|  | 103 | WAIT_FOR_NETWORK=1 | 
|  | 104 | ;; | 
|  | 105 | b) | 
|  | 106 | BALLOONING=1 | 
|  | 107 | ;; | 
|  | 108 | f) | 
|  | 109 | VPX_FILE=$OPTARG | 
|  | 110 | ;; | 
|  | 111 | d) | 
|  | 112 | DATA_VDI_SIZE="${OPTARG}MiB" | 
|  | 113 | ;; | 
|  | 114 | m) | 
|  | 115 | BRIDGE_M=$OPTARG | 
|  | 116 | ;; | 
|  | 117 | p) | 
|  | 118 | BRIDGE_P=$OPTARG | 
|  | 119 | ;; | 
|  | 120 | k) | 
|  | 121 | KERNEL_PARAMS=$OPTARG | 
|  | 122 | ;; | 
|  | 123 | r) | 
|  | 124 | RAM=$OPTARG | 
|  | 125 | ;; | 
|  | 126 | v) | 
|  | 127 | BRIDGE_V=$OPTARG | 
|  | 128 | ;; | 
|  | 129 | ?) | 
|  | 130 | usage | 
|  | 131 | exit | 
|  | 132 | ;; | 
|  | 133 | esac | 
|  | 134 | done | 
|  | 135 | if [[ -z $BRIDGE_M ]] | 
|  | 136 | then | 
|  | 137 | BRIDGE_M=xenbr0 | 
|  | 138 | fi | 
|  | 139 | } | 
|  | 140 |  | 
|  | 141 |  | 
|  | 142 | xe_min() | 
|  | 143 | { | 
|  | 144 | local cmd="$1" | 
|  | 145 | shift | 
|  | 146 | xe "$cmd" --minimal "$@" | 
|  | 147 | } | 
|  | 148 |  | 
|  | 149 |  | 
|  | 150 | get_dest_sr() | 
|  | 151 | { | 
|  | 152 | IFS=, | 
|  | 153 | sr_uuids=$(xe sr-list --minimal other-config:i18n-key=local-storage) | 
|  | 154 | dest_sr="" | 
|  | 155 | for sr_uuid in $sr_uuids | 
|  | 156 | do | 
|  | 157 | pbd=$(xe pbd-list --minimal sr-uuid=$sr_uuid host-uuid=$INSTALLATION_UUID) | 
|  | 158 | if [ "$pbd" ] | 
|  | 159 | then | 
|  | 160 | echo "$sr_uuid" | 
|  | 161 | unset IFS | 
|  | 162 | return | 
|  | 163 | fi | 
|  | 164 | done | 
|  | 165 | unset IFS | 
|  | 166 |  | 
|  | 167 | dest_sr=$(xe_min sr-list uuid=$(xe_min pool-list params=default-SR)) | 
|  | 168 | if [ "$dest_sr" = "" ] | 
|  | 169 | then | 
|  | 170 | echo "No local storage and no default storage; cannot import VPX." >&2 | 
|  | 171 | exit 1 | 
|  | 172 | else | 
|  | 173 | echo "$dest_sr" | 
|  | 174 | fi | 
|  | 175 | } | 
|  | 176 |  | 
|  | 177 |  | 
|  | 178 | find_network() | 
|  | 179 | { | 
|  | 180 | result=$(xe_min network-list bridge="$1") | 
|  | 181 | if [ "$result" = "" ] | 
|  | 182 | then | 
|  | 183 | result=$(xe_min network-list name-label="$1") | 
|  | 184 | fi | 
|  | 185 | echo "$result" | 
|  | 186 | } | 
|  | 187 |  | 
|  | 188 |  | 
|  | 189 | find_template() | 
|  | 190 | { | 
|  | 191 | xe_min template-list other-config:os-vpx=true | 
|  | 192 | } | 
|  | 193 |  | 
|  | 194 |  | 
|  | 195 | renumber_system_disk() | 
|  | 196 | { | 
|  | 197 | local v="$1" | 
|  | 198 | local vdi_uuid=$(xe_min vbd-list vm-uuid="$v" type=Disk userdevice=xvda \ | 
|  | 199 | params=vdi-uuid) | 
|  | 200 | if [ "$vdi_uuid" ] | 
|  | 201 | then | 
|  | 202 | local vbd_uuid=$(xe_min vbd-list vm-uuid="$v" vdi-uuid="$vdi_uuid") | 
|  | 203 | xe vbd-destroy uuid="$vbd_uuid" | 
|  | 204 | local new_vbd_uuid=$(xe vbd-create vm-uuid="$v" vdi-uuid="$vdi_uuid" \ | 
|  | 205 | device=0 bootable=true type=Disk) | 
|  | 206 | xe vbd-param-set other-config:owner uuid="$new_vbd_uuid" | 
|  | 207 | fi | 
|  | 208 | } | 
|  | 209 |  | 
|  | 210 |  | 
|  | 211 | create_vif() | 
|  | 212 | { | 
|  | 213 | xe vif-create vm-uuid="$1" network-uuid="$2" device="$3" | 
|  | 214 | } | 
|  | 215 |  | 
|  | 216 | create_gi_vif() | 
|  | 217 | { | 
|  | 218 | local v="$1" | 
|  | 219 | # Note that we've made the outbound device eth1, so that it comes up after | 
|  | 220 | # the guest installer VIF, which means that the outbound one wins in terms | 
|  | 221 | # of gateway. | 
|  | 222 | local gi_network_uuid=$(xe_min network-list \ | 
|  | 223 | other-config:is_guest_installer_network=true) | 
|  | 224 | create_vif "$v" "$gi_network_uuid" "0" >/dev/null | 
|  | 225 | } | 
|  | 226 |  | 
|  | 227 | create_vm_vif() | 
|  | 228 | { | 
|  | 229 | local v="$1" | 
|  | 230 | echo "Installing management interface on $BRIDGE_V." | 
|  | 231 | local out_network_uuid=$(find_network "$BRIDGE_V") | 
|  | 232 | create_vif "$v" "$out_network_uuid" "1" >/dev/null | 
|  | 233 | } | 
|  | 234 |  | 
|  | 235 | create_management_vif() | 
|  | 236 | { | 
|  | 237 | local v="$1" | 
|  | 238 | echo "Installing management interface on $BRIDGE_M." | 
|  | 239 | local out_network_uuid=$(find_network "$BRIDGE_M") | 
|  | 240 | create_vif "$v" "$out_network_uuid" "2" >/dev/null | 
|  | 241 | } | 
|  | 242 |  | 
|  | 243 |  | 
|  | 244 | # This installs the interface for public traffic, only if a bridge is specified | 
|  | 245 | # The interface is not configured at this stage, but it will be, once the admin | 
|  | 246 | # tasks are complete for the services of this VPX | 
|  | 247 | create_public_vif() | 
|  | 248 | { | 
|  | 249 | local v="$1" | 
|  | 250 | if [[ -z $BRIDGE_P ]] | 
|  | 251 | then | 
|  | 252 | echo "Skipping installation of interface for public traffic." | 
|  | 253 | else | 
|  | 254 | echo "Installing public interface on $BRIDGE_P." | 
|  | 255 | pub_network_uuid=$(find_network "$BRIDGE_P") | 
|  | 256 | create_vif "$v" "$pub_network_uuid" "3" >/dev/null | 
|  | 257 | fi | 
|  | 258 | } | 
|  | 259 |  | 
|  | 260 |  | 
|  | 261 | label_system_disk() | 
|  | 262 | { | 
|  | 263 | local v="$1" | 
|  | 264 | local vdi_uuid=$(xe_min vbd-list vm-uuid="$v" type=Disk userdevice=0 \ | 
|  | 265 | params=vdi-uuid) | 
|  | 266 | xe vdi-param-set \ | 
|  | 267 | name-label="$NAME system disk" \ | 
|  | 268 | other-config:os-vpx=true \ | 
|  | 269 | uuid=$vdi_uuid | 
|  | 270 | } | 
|  | 271 |  | 
|  | 272 |  | 
|  | 273 | create_data_disk() | 
|  | 274 | { | 
|  | 275 | local v="$1" | 
|  | 276 |  | 
|  | 277 | local sys_vdi_uuid=$(xe_min vbd-list vm-uuid="$v" type=Disk params=vdi-uuid) | 
|  | 278 | local data_vdi_uuid=$(xe_min vdi-list other-config:os-vpx-data=true) | 
|  | 279 |  | 
|  | 280 | if echo "$data_vdi_uuid" | grep -q , | 
|  | 281 | then | 
|  | 282 | echo "Multiple data disks found -- assuming that you want a new one." | 
|  | 283 | data_vdi_uuid="" | 
|  | 284 | else | 
|  | 285 | data_in_use=$(xe_min vbd-list vdi-uuid="$data_vdi_uuid") | 
|  | 286 | if [ "$data_in_use" != "" ] | 
|  | 287 | then | 
|  | 288 | echo "Data disk already in use -- will create another one." | 
|  | 289 | data_vdi_uuid="" | 
|  | 290 | fi | 
|  | 291 | fi | 
|  | 292 |  | 
|  | 293 | if [ "$data_vdi_uuid" = "" ] | 
|  | 294 | then | 
|  | 295 | echo -n "Creating new data disk ($DATA_VDI_SIZE)... " | 
|  | 296 | sr_uuid=$(xe_min vdi-list params=sr-uuid uuid="$sys_vdi_uuid") | 
|  | 297 | data_vdi_uuid=$(xe vdi-create name-label="$NAME data disk" \ | 
|  | 298 | sr-uuid="$sr_uuid" \ | 
|  | 299 | type=user \ | 
|  | 300 | virtual-size="$DATA_VDI_SIZE") | 
|  | 301 | xe vdi-param-set \ | 
|  | 302 | other-config:os-vpx-data=true \ | 
|  | 303 | uuid="$data_vdi_uuid" | 
|  | 304 | dom0_uuid=$(xe_min vm-list is-control-domain=true) | 
|  | 305 | vbd_uuid=$(xe vbd-create device=autodetect type=Disk \ | 
|  | 306 | vdi-uuid="$data_vdi_uuid" vm-uuid="$dom0_uuid") | 
|  | 307 | xe vbd-plug uuid=$vbd_uuid | 
|  | 308 | dev=$(xe_min vbd-list params=device uuid=$vbd_uuid) | 
|  | 309 | mke2fs -q -j -m0 /dev/$dev | 
|  | 310 | e2label /dev/$dev vpxstate | 
|  | 311 | xe vbd-unplug uuid=$vbd_uuid | 
|  | 312 | xe vbd-destroy uuid=$vbd_uuid | 
|  | 313 | else | 
|  | 314 | echo -n "Attaching old data disk... " | 
|  | 315 | fi | 
|  | 316 | vbd_uuid=$(xe vbd-create device=2 type=Disk \ | 
|  | 317 | vdi-uuid="$data_vdi_uuid" vm-uuid="$v") | 
|  | 318 | xe vbd-param-set other-config:os-vpx-data=true uuid=$vbd_uuid | 
|  | 319 | echo "done." | 
|  | 320 | } | 
|  | 321 |  | 
|  | 322 |  | 
|  | 323 | set_kernel_params() | 
|  | 324 | { | 
|  | 325 | local v="$1" | 
|  | 326 | local args=$KERNEL_PARAMS | 
|  | 327 | local cmdline=$(cat /proc/cmdline) | 
|  | 328 | for word in $cmdline | 
|  | 329 | do | 
|  | 330 | if echo "$word" | grep -q "geppetto" | 
|  | 331 | then | 
|  | 332 | args="$word $args" | 
|  | 333 | fi | 
|  | 334 | done | 
|  | 335 | if [ "$args" != "" ] | 
|  | 336 | then | 
|  | 337 | echo "Passing Geppetto args to VPX: $args." | 
|  | 338 | xe vm-param-set PV-args="$args" uuid="$v" | 
|  | 339 | fi | 
|  | 340 | } | 
|  | 341 |  | 
|  | 342 |  | 
|  | 343 | set_memory() | 
|  | 344 | { | 
|  | 345 | local v="$1" | 
|  | 346 | if [ "$RAM" != "" ] | 
|  | 347 | then | 
|  | 348 | echo "Setting RAM to $RAM MiB." | 
|  | 349 | [ "$BALLOONING" == 1 ] && RAM_MIN=$(($RAM / 2)) || RAM_MIN=$RAM | 
|  | 350 | xe vm-memory-limits-set static-min=16MiB static-max=${RAM}MiB \ | 
|  | 351 | dynamic-min=${RAM_MIN}MiB dynamic-max=${RAM}MiB \ | 
|  | 352 | uuid="$v" | 
|  | 353 | fi | 
|  | 354 | } | 
|  | 355 |  | 
|  | 356 |  | 
|  | 357 | # Make the VM auto-start on server boot. | 
|  | 358 | set_auto_start() | 
|  | 359 | { | 
|  | 360 | local v="$1" | 
|  | 361 | xe vm-param-set uuid="$v" other-config:auto_poweron=true | 
|  | 362 | } | 
|  | 363 |  | 
|  | 364 |  | 
|  | 365 | set_all() | 
|  | 366 | { | 
|  | 367 | local v="$1" | 
|  | 368 | set_kernel_params "$v" | 
|  | 369 | set_memory "$v" | 
|  | 370 | set_auto_start "$v" | 
|  | 371 | label_system_disk "$v" | 
|  | 372 | create_gi_vif "$v" | 
|  | 373 | create_vm_vif "$v" | 
|  | 374 | create_management_vif "$v" | 
|  | 375 | create_public_vif "$v" | 
|  | 376 | } | 
|  | 377 |  | 
|  | 378 |  | 
|  | 379 | log_vifs() | 
|  | 380 | { | 
|  | 381 | local v="$1" | 
|  | 382 |  | 
|  | 383 | (IFS=, | 
|  | 384 | for vif in $(xe_min vif-list vm-uuid="$v") | 
|  | 385 | do | 
|  | 386 | dev=$(xe_min vif-list uuid="$vif" params=device) | 
|  | 387 | mac=$(xe_min vif-list uuid="$vif" params=MAC | sed -e 's/:/-/g') | 
|  | 388 | echo "eth$dev has MAC $mac." | 
|  | 389 | done | 
|  | 390 | unset IFS) | sort | 
|  | 391 | } | 
|  | 392 |  | 
|  | 393 |  | 
|  | 394 | destroy_vifs() | 
|  | 395 | { | 
|  | 396 | local v="$1" | 
|  | 397 | IFS=, | 
|  | 398 | for vif in $(xe_min vif-list vm-uuid="$v") | 
|  | 399 | do | 
|  | 400 | xe vif-destroy uuid="$vif" | 
|  | 401 | done | 
|  | 402 | unset IFS | 
|  | 403 | } | 
|  | 404 |  | 
|  | 405 |  | 
|  | 406 | get_params "$@" | 
|  | 407 |  | 
|  | 408 | thisdir=$(dirname "$0") | 
|  | 409 |  | 
|  | 410 | if [ "$FROM_TEMPLATE" ] | 
|  | 411 | then | 
|  | 412 | template_uuid=$(find_template) | 
|  | 413 | name=$(xe_min template-list params=name-label uuid="$template_uuid") | 
|  | 414 | echo -n "Cloning $name... " | 
|  | 415 | vm_uuid=$(xe vm-clone vm="$template_uuid" new-name-label="$name") | 
|  | 416 | xe vm-param-set is-a-template=false uuid="$vm_uuid" | 
|  | 417 | echo $vm_uuid. | 
|  | 418 |  | 
|  | 419 | destroy_vifs "$vm_uuid" | 
|  | 420 | set_all "$vm_uuid" | 
|  | 421 | else | 
|  | 422 | if [ ! -f "$VPX_FILE" ] | 
|  | 423 | then | 
|  | 424 | # Search $thisdir/$VPX_FILE too.  In particular, this is used when | 
|  | 425 | # installing the VPX from the supp-pack, because we want to be able to | 
|  | 426 | # invoke this script from the RPM and the firstboot script. | 
|  | 427 | if [ -f "$thisdir/$VPX_FILE" ] | 
|  | 428 | then | 
|  | 429 | VPX_FILE="$thisdir/$VPX_FILE" | 
|  | 430 | else | 
|  | 431 | echo "$VPX_FILE does not exist." >&2 | 
|  | 432 | exit 1 | 
|  | 433 | fi | 
|  | 434 | fi | 
|  | 435 |  | 
|  | 436 | echo "Found OS-VPX File: $VPX_FILE. " | 
|  | 437 |  | 
|  | 438 | dest_sr=$(get_dest_sr) | 
|  | 439 |  | 
|  | 440 | echo -n "Installing $NAME... " | 
|  | 441 | vm_uuid=$(xe vm-import filename=$VPX_FILE sr-uuid="$dest_sr") | 
|  | 442 | echo $vm_uuid. | 
|  | 443 |  | 
|  | 444 | renumber_system_disk "$vm_uuid" | 
|  | 445 |  | 
|  | 446 | nl=$(xe_min vm-list params=name-label uuid=$vm_uuid) | 
|  | 447 | xe vm-param-set \ | 
|  | 448 | "name-label=${nl/ import/}" \ | 
|  | 449 | other-config:os-vpx=true \ | 
|  | 450 | uuid=$vm_uuid | 
|  | 451 |  | 
|  | 452 | set_all "$vm_uuid" | 
|  | 453 | create_data_disk "$vm_uuid" | 
|  | 454 |  | 
|  | 455 | if [ "$AS_TEMPLATE" ] | 
|  | 456 | then | 
|  | 457 | xe vm-param-set uuid="$vm_uuid" is-a-template=true \ | 
|  | 458 | other-config:instant=true | 
|  | 459 | echo -n "Installing VPX from template... " | 
|  | 460 | vm_uuid=$(xe vm-clone vm="$vm_uuid" new-name-label="${nl/ import/}") | 
|  | 461 | xe vm-param-set is-a-template=false uuid="$vm_uuid" | 
|  | 462 | echo "$vm_uuid." | 
|  | 463 | fi | 
|  | 464 | fi | 
|  | 465 |  | 
|  | 466 |  | 
|  | 467 | log_vifs "$vm_uuid" | 
|  | 468 |  | 
|  | 469 | echo -n "Starting VM... " | 
|  | 470 | xe vm-start uuid=$vm_uuid | 
|  | 471 | echo "done." | 
|  | 472 |  | 
|  | 473 |  | 
|  | 474 | show_ip() | 
|  | 475 | { | 
|  | 476 | ip_addr=$(echo "$1" | sed -n "s,^.*"$2"/ip: \([^;]*\).*$,\1,p") | 
|  | 477 | echo -n "IP address for $3: " | 
|  | 478 | if [ "$ip_addr" = "" ] | 
|  | 479 | then | 
|  | 480 | echo "did not appear." | 
|  | 481 | else | 
|  | 482 | echo "$ip_addr." | 
|  | 483 | fi | 
|  | 484 | } | 
|  | 485 |  | 
|  | 486 |  | 
|  | 487 | if [ "$WAIT_FOR_NETWORK" ] | 
|  | 488 | then | 
|  | 489 | echo "Waiting for network configuration... " | 
|  | 490 | i=0 | 
|  | 491 | while [ $i -lt 600 ] | 
|  | 492 | do | 
|  | 493 | ip=$(xe_min vm-list params=networks uuid=$vm_uuid) | 
|  | 494 | if [ "$ip" != "<not in database>" ] | 
|  | 495 | then | 
|  | 496 | show_ip "$ip" "1" "$BRIDGE_M" | 
|  | 497 | if [[ $BRIDGE_P ]] | 
|  | 498 | then | 
|  | 499 | show_ip "$ip" "2" "$BRIDGE_P" | 
|  | 500 | fi | 
|  | 501 | echo "Installation complete." | 
|  | 502 | exit 0 | 
|  | 503 | fi | 
|  | 504 | sleep 10 | 
|  | 505 | let i=i+1 | 
|  | 506 | done | 
|  | 507 | fi |