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  }