github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/tests/includes/wait-for.sh (about) 1 # SHORT_TIMEOUT is the time between polling attempts. 2 SHORT_TIMEOUT=5 3 4 # wait_for defines the ability to wait for a given condition to happen in a 5 # juju status output. The output is JSON, so everything that the API server 6 # knows about should be valid. 7 # The query argument is a jq query. 8 # The default timeout is 10 minutes. You can change this by providing the 9 # timeout argument (an integer number of seconds). 10 # 11 # ``` 12 # wait_for <model name> <query> [<timeout>] 13 # ``` 14 wait_for() { 15 local name query timeout 16 17 name=${1} 18 query=${2} 19 timeout=${3:-600} # default timeout: 600s = 10m 20 21 attempt=0 22 start_time="$(date -u +%s)" 23 # shellcheck disable=SC2046,SC2143 24 until [[ "$(juju status --format=json 2>/dev/null | jq -S "${query}" | grep "${name}")" ]]; do 25 echo "[+] (attempt ${attempt}) polling status for" "${query} => ${name}" 26 juju status --relations 2>&1 | sed 's/^/ | /g' 27 sleep "${SHORT_TIMEOUT}" 28 29 elapsed=$(date -u +%s)-$start_time 30 if [[ ${elapsed} -ge ${timeout} ]]; then 31 echo "[-] $(red 'timed out waiting for')" "$(red "${name}")" 32 exit 1 33 fi 34 35 attempt=$((attempt + 1)) 36 done 37 38 if [[ ${attempt} -gt 0 ]]; then 39 echo "[+] $(green 'Completed polling status for')" "$(green "${name}")" 40 juju status --relations 2>&1 | sed 's/^/ | /g' 41 # Although juju reports as an idle condition, some charms require a 42 # breathe period to ensure things have actually settled. 43 sleep "${SHORT_TIMEOUT}" 44 fi 45 } 46 47 idle_condition() { 48 local name app_index unit_index 49 50 name=${1} 51 app_index=${2:-0} 52 unit_index=${3:-0} 53 54 path=".[\"$name\"] | .units | .[\"$name/$unit_index\"]" 55 56 echo ".applications | select(($path | .[\"juju-status\"] | .current == \"idle\") and ($path | .[\"workload-status\"] | .current != \"error\")) | keys[$app_index]" 57 } 58 59 active_idle_condition() { 60 local name app_index unit_index 61 62 name=${1} 63 app_index=${2:-0} 64 unit_index=${3:-0} 65 66 path=".[\"$name\"] | .units | .[\"$name/$unit_index\"]" 67 68 echo ".applications | select(($path | .[\"juju-status\"] | .current == \"idle\") and ($path | .[\"workload-status\"] | .current == \"active\")) | keys[$app_index]" 69 } 70 71 idle_subordinate_condition() { 72 local name parent unit_index 73 74 name=${1} 75 parent=${2} 76 unit_index=${3:-0} 77 78 path=".[\"$parent\"] | .units | .[] | .subordinates | .[\"$name/$unit_index\"]" 79 80 # Print the *subordinate* name if it has an idle status in parent application 81 echo ".applications | select(($path | .[\"juju-status\"] | .current == \"idle\") and ($path | .[\"workload-status\"] | .current != \"error\")) | \"$name\"" 82 } 83 84 active_condition() { 85 local name app_index 86 87 name=${1} 88 app_index=${2:-0} 89 90 echo ".applications | select(.[\"$name\"] | .[\"application-status\"] | .current == \"active\") | keys[$app_index]" 91 } 92 93 # not_idle_list should be used where you expect an arbitrary list of applications whose agent-status are not in idle state, 94 # ideally applications in a bundle, this helps the tests to avoid being overly specific to a given number of applications. 95 # e.g. wait_for 0 "$(not_idle_list) | length" 1800 96 not_idle_list() { 97 echo '[.applications[] | select((.units[] | .["juju-status"].current != "idle") or (.units[] | .["workload-status"].current == "error"))]' 98 } 99 100 # workload_status gets the workload-status object for the unit - use 101 # .current or .message to select the actual field you need. 102 workload_status() { 103 local app unit 104 105 app=$1 106 unit=$2 107 108 echo ".applications[\"$app\"].units[\"$app/$unit\"][\"workload-status\"]" 109 } 110 111 # agent_status gets the juju-status object for the unit - use 112 # .current or .message to select the actual field you need. 113 agent_status() { 114 local app unit 115 116 app=$1 117 unit=$2 118 119 echo ".applications[\"$app\"].units[\"$app/$unit\"][\"juju-status\"]" 120 } 121 122 # charm_rev gets the current juju-status object for the application and uses it 123 # to find the application charm-rev. 124 charm_rev() { 125 local app rev 126 127 app=$1 128 rev=${2:-0} 129 130 echo ".applications | select(.[\"$app\"] | .[\"charm-rev\"] == $rev)" 131 } 132 133 # charm_channel gets the current juju-status object for the application and uses it 134 # to find the application charm-channel. 135 charm_channel() { 136 local app channel 137 138 app=$1 139 channel=$2 140 141 echo ".applications | select(.[\"$app\"] | .[\"charm-channel\"] == \"$channel\")" 142 } 143 144 # wait_for_machine_agent_status blocks until the machine agent for the specified 145 # machine instance ID reports the requested status. 146 # 147 # ``` 148 # wait_for_machine_agent_status <instance-id> <status> 149 # 150 # example: 151 # wait_for_machine_agent_status "i-1234" "started" 152 # ``` 153 wait_for_machine_agent_status() { 154 local inst_id status 155 156 inst_id=${1} 157 status=${2} 158 159 attempt=0 160 # shellcheck disable=SC2046,SC2143 161 until [ $(juju show-machine --format json | jq -r ".[\"machines\"] | .[\"${inst_id}\"] | .[\"juju-status\"] | .[\"current\"]" | grep "${status}") ]; do 162 echo "[+] (attempt ${attempt}) polling machines" 163 juju machines | grep "$inst_id" 2>&1 | sed 's/^/ | /g' 164 sleep "${SHORT_TIMEOUT}" 165 attempt=$((attempt + 1)) 166 done 167 168 if [[ ${attempt} -gt 0 ]]; then 169 echo "[+] $(green 'Completed polling machines')" 170 juju machines | grep "$inst_id" 2>&1 | sed 's/^/ | /g' 171 sleep "${SHORT_TIMEOUT}" 172 fi 173 } 174 175 # wait_for_machine_netif_count blocks until the number of detected network 176 # interfaces for the requested machine instance ID becomes equal to the desired 177 # value. 178 # 179 # ``` 180 # wait_for_machine_netif_count <instance-id> <count> 181 # 182 # example: 183 # wait_for_machine_netif_count "i-1234" "42" 184 # ``` 185 wait_for_machine_netif_count() { 186 local inst_id count 187 188 inst_id=${1} 189 count=${2} 190 191 attempt=0 192 # shellcheck disable=SC2046,SC2143 193 until [ $(juju show-machine --format json | jq -r ".[\"machines\"] | .[\"${inst_id}\"] | .[\"network-interfaces\"] | length" | grep "${count}") ]; do 194 # shellcheck disable=SC2046,SC2143 195 echo "[+] (attempt ${attempt}) network interface count for instance ${inst_id} = "$(juju show-machine --format json | jq -r ".[\"machines\"] | .[\"${inst_id}\"] | .[\"network-interfaces\"] | length") 196 sleep "${SHORT_TIMEOUT}" 197 attempt=$((attempt + 1)) 198 done 199 200 } 201 202 # wait_for_subordinate_count blocks until the number of subordinates 203 # to the desired unit becomes equal to the desired value. 204 # 205 # ``` 206 # wait_for_subordinate_count <application name> <principal unit num> <count> 207 # 208 # example: 209 # wait_for_subordinate_count mysql 0 3 210 # ``` 211 wait_for_subordinate_count() { 212 local name unit_index count 213 214 name=${1} 215 unit_index=${2:-0} 216 count=${3:-0} 217 218 attempt=0 219 # shellcheck disable=SC2046,SC2143 220 until [ $(juju status --format json | jq -r ".applications | .[\"${name}\"] | .units | .[\"${name}/${unit_index}\"] | .subordinates | length" | grep "${count}") ]; do 221 # shellcheck disable=SC2046,SC2143 222 echo "[+] (attempt ${attempt}) subordinate count for unit ${name}/${unit_index} = "$(juju status --format json | jq -r ".applications | .[\"${name}\"] | .units | .[\"${name}/${unit_index}\"] | .subordinates | length") 223 sleep "${SHORT_TIMEOUT}" 224 attempt=$((attempt + 1)) 225 done 226 227 if [[ ${attempt} -gt 0 ]]; then 228 echo "[+] $(green 'Completed polling status')" 229 juju status 2>&1 | sed 's/^/ | /g' 230 sleep "${SHORT_TIMEOUT}" 231 fi 232 } 233 234 # wait_for_model blocks until a model appears 235 # interfaces for the requested machine instance ID becomes equal to the desired 236 # value. 237 # 238 # ``` 239 # wait_for_model <name> 240 # 241 # example: 242 # wait_for_model "default" 243 # ``` 244 wait_for_model() { 245 local name 246 247 name=${1} 248 249 attempt=0 250 # shellcheck disable=SC2046,SC2143 251 until [ $(juju models --format=json | jq -r ".models | .[] | select(.[\"short-name\"] == \"${name}\") | .[\"short-name\"]" | grep "${name}") ]; do 252 echo "[+] (attempt ${attempt}) polling models" 253 juju models | sed 's/^/ | /g' 254 sleep "${SHORT_TIMEOUT}" 255 attempt=$((attempt + 1)) 256 done 257 258 if [[ ${attempt} -gt 0 ]]; then 259 echo "[+] $(green 'Completed polling models')" 260 juju models | sed 's/^/ | /g' 261 sleep "${SHORT_TIMEOUT}" 262 fi 263 } 264 265 # wait_for_systemd_service_files_to_appear blocks until the systemd service 266 # file for a unit is written to disk. 267 # 268 # ``` 269 # wait_for_systemd_service_files_to_appear <unit_name> 270 # 271 # example: 272 # wait_for_systemd_service_files_to_appear "ubuntu/0" 273 # ``` 274 wait_for_systemd_service_files_to_appear() { 275 local unit 276 277 unit=${1} 278 # shellcheck disable=SC2086 279 svc_file_path="/etc/systemd/system/jujud-unit-$(echo -n ${1} | tr '/' '-').service" 280 281 attempt=0 282 # shellcheck disable=SC2046,SC2143 283 while [ "$attempt" != "3" ]; do 284 echo "[+] (attempt ${attempt}) waiting for the systemd unit files for ${unit} to appear" 285 286 svc_present=$(juju ssh "${unit}" "ls ${svc_file_path} 2>/dev/null || echo -n 'missing'") 287 if [[ ${svc_present} != "missing" ]]; then 288 echo "[+] systemd unit files for ${unit} are now available" 289 return 290 fi 291 292 sleep "${SHORT_TIMEOUT}" 293 attempt=$((attempt + 1)) 294 done 295 296 # shellcheck disable=SC2046,SC2005 297 echo $(red "Timed out waiting for the systemd unit files for ${unit} to appear") 298 exit 1 299 } 300 301 # wait_for_storage is like wait_for but for storage formats. Used to wait for a certain condition in charm storage. 302 wait_for_storage() { 303 local name query timeout 304 305 name=${1} 306 query=${2} 307 timeout=${3:-600} # default timeout: 600s = 10m 308 309 attempt=0 310 start_time="$(date -u +%s)" 311 # shellcheck disable=SC2046,SC2143 312 until [[ "$(juju storage --format=json 2>/dev/null | jq "${query}" | grep "${name}")" ]]; do 313 echo "[+] (attempt ${attempt}) polling status for" "${query} => ${name}" 314 juju storage 2>&1 | sed 's/^/ | /g' 315 sleep "${SHORT_TIMEOUT}" 316 317 elapsed=$(date -u +%s)-$start_time 318 if [[ ${elapsed} -ge ${timeout} ]]; then 319 echo "[-] $(red 'timed out waiting for')" "$(red "${name}")" 320 exit 1 321 fi 322 323 attempt=$((attempt + 1)) 324 done 325 326 if [[ ${attempt} -gt 0 ]]; then 327 echo "[+] $(green 'Completed polling status for')" "$(green "${name}")" 328 juju storage 2>&1 | sed 's/^/ | /g' 329 # Although juju reports as an idle condition, some charms require a 330 # breathe period to ensure things have actually settled. 331 sleep "${SHORT_TIMEOUT}" 332 fi 333 } 334 335 # wait_for_aws_ingress_cidrs_for_port_range blocks until the expected CIDRs 336 # are present in the AWS security group rules for the specified port range. 337 wait_for_aws_ingress_cidrs_for_port_range() { 338 local from_port to_port exp_cidrs cidr_type 339 340 from_port=${1} 341 to_port=${2} 342 exp_cidrs=${3} 343 cidr_type=${4} 344 345 ipV6Suffix="" 346 if [ "$cidr_type" = "ipv6" ]; then 347 ipV6Suffix="v6" 348 fi 349 350 # shellcheck disable=SC2086 351 secgrp_list=$(aws ec2 describe-security-groups --filters Name=ip-permission.from-port,Values=${from_port} Name=ip-permission.to-port,Values=${to_port}) 352 # print the security group rules 353 # shellcheck disable=SC2086 354 got_cidrs=$(echo ${secgrp_list} | jq -r ".SecurityGroups[0].IpPermissions // [] | .[] | select(.FromPort == ${from_port} and .ToPort == ${to_port}) | .Ip${ipV6Suffix}Ranges // [] | .[] | .CidrIp${ipV6Suffix}" | sort | paste -sd, -) 355 356 attempt=0 357 # shellcheck disable=SC2046,SC2143 358 while [ "$attempt" -lt "3" ]; do 359 echo "[+] (attempt ${attempt}) polling security group rules" 360 # shellcheck disable=SC2086 361 secgrp_list=$(aws ec2 describe-security-groups --filters Name=ip-permission.from-port,Values=${from_port} Name=ip-permission.to-port,Values=${to_port}) 362 # shellcheck disable=SC2086 363 got_cidrs=$(echo ${secgrp_list} | jq -r ".SecurityGroups[0].IpPermissions // [] | .[] | select(.FromPort == ${from_port} and .ToPort == ${to_port}) | .Ip${ipV6Suffix}Ranges // [] | .[] | .CidrIp${ipV6Suffix}" | sort | paste -sd, -) 364 sleep "${SHORT_TIMEOUT}" 365 366 if [ "$got_cidrs" == "$exp_cidrs" ]; then 367 break 368 fi 369 370 attempt=$((attempt + 1)) 371 done 372 373 if [ "$got_cidrs" != "$exp_cidrs" ]; then 374 # shellcheck disable=SC2046 375 echo $(red "expected generated EC2 ${cidr_type} ingress CIDRs for range [${from_port}, ${to_port}] to be:\n${exp_cidrs}\nGOT:\n${got_cidrs}") 376 exit 1 377 fi 378 379 echo "[+] security group rules for port range [${from_port}, ${to_port}] and CIDRs ${exp_cidrs} updated" 380 }