Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global image for external #5553

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
37d0c30
Added initial concept of configurable image for external
judovana Sep 2, 2024
0ae0514
Added proper defautls to survive with -u
judovana Sep 2, 2024
e415d58
Added forgotten use of providers in cleaning of images for build_all
judovana Sep 2, 2024
5047445
Removed usage of variable in method name
judovana Sep 2, 2024
3a1b749
excluded EXTERNAL_AQA_IMAGE from build_all.sh
judovana Sep 3, 2024
9f3f031
Now honor EXTERNAL_AQA_CONTAINER_CLEAN also after external run
judovana Sep 4, 2024
520656f
Now using the userset image (if set)
judovana Sep 4, 2024
5f88915
reworked roeprties so they can work in multi-image environment
judovana Sep 4, 2024
b028d4a
Added explicit search for java in /opt/java/openjdk
judovana Sep 5, 2024
0a3f3db
Added set of warning for case the mounted jdk is missing or TEST_JAVA…
judovana Sep 5, 2024
b1e20d1
Added example of to build_image.sh
judovana Sep 12, 2024
a3638b3
Retuerned parsing ov version. It is used over all scripts, but not set
judovana Sep 12, 2024
b47ef1e
Deduct os on correct place, after the hack is applied
judovana Sep 12, 2024
26cd87f
Exiting build_all if version is missing. he variable is used (wihtout…
judovana Sep 12, 2024
cecf323
Added missign make dependecies to all systems for criu
judovana Sep 16, 2024
82b1c78
Removed common depenencies
judovana Sep 23, 2024
e36ff60
Properly expand build-essentials which are not in fedora, and ascii doc
judovana Sep 25, 2024
172a1f5
Changed decvlaration of pacakges in criu-ubi-portable-checkpoint to g…
judovana Sep 28, 2024
0839a27
Improved exit message for unknown os
judovana Sep 28, 2024
673b223
better readme for EXTERNAL_AQA_IMAGE
judovana Oct 7, 2024
849d2bd
decalred -> declared
judovana Oct 31, 2024
b7f25af
Removed the nasty warning warning... by nicer header
judovana Oct 31, 2024
14dd897
Update external/build_all.sh
judovana Nov 6, 2024
d8acc30
Update external/common_functions.sh
judovana Nov 6, 2024
a1db200
Added whitesapces between # and first char
judovana Nov 6, 2024
e4b11c4
More === text ===.. isntead of text text text
judovana Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions external/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ EXTERNAL_AQA_CONTAINER_CLEAN=true|false
```
If EXTERNAL_AQA_CONTAINER_CLEAN is false, then the image is not cleaned after the `make _tests...` targets are finished.

## Configuring base image
By default, Eclipse Temurin JDK of identical version as your JDK is used. You can see, that `print_image_args` is taking all arguments to properly set registry url, image name and version. `EXTERNAL_AQA_IMAGE` variable describes the usual image ID in form of `optional_registry/path/name:tag` to allow for alternate images besides the default to be used.

## Running External tests locally
To run any AQA tests locally, you follow the same pattern:
Expand Down
2 changes: 1 addition & 1 deletion external/build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
<fileset dir="${src}/" includes="*.sh, *.properties"/>
</copy>
<copy todir="${DEST_EXTERNAL}">
<fileset dir="${top}" includes="*.sh"/>
<fileset dir="${top}" includes="*.sh common.properties"/>
</copy>
</target>

Expand Down
10 changes: 10 additions & 0 deletions external/build_all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ set -o pipefail

source $(dirname "$0")/common_functions.sh

if [ -n "${EXTERNAL_AQA_IMAGE}" ] ; then\
echo "EXTERNAL_AQA_IMAGE is declared as $EXTERNAL_AQA_IMAGE; it would break this script. Unset it."
exit 1
fi

if [ -z "${version}" ] ; then\
echo "'version' (of JDK, e.g., 17) is mandatory variable for this script to run"
exit 1
fi

# Cleanup any old containers and images
echo "==============================================================================="
echo " Cleaning up images "
Expand Down
2 changes: 2 additions & 0 deletions external/build_image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ if [ $# -ne 9 ] && [ $# -ne 10 ]; then
echo "The supported tests are ${supported_tests}"
echo
echo "usage: $0 test version vm os package build platform check_external_custom"
echo "eg: $0 jacoco 17 openj9 ubi jdk full linux_x86-64 default 0"
echo "test = ${supported_tests}"
echo "vm = ${supported_jvms}"
echo "os = ${supported_os}"
Expand All @@ -43,6 +44,7 @@ check_external_custom=$9
if [[ "${check_external_custom}" == "0" ]]; then
set_test $1
fi
set_version $2
set_vm $3
set_os $4
set_package $5
Expand Down
2 changes: 1 addition & 1 deletion external/camel/test.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
test_results="testResults"
tag_version="2.7.0"
environment_variable="MODE=java"
ubuntu_packages="git"
generic_packages="git"
6 changes: 6 additions & 0 deletions external/common.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# those packages are included in all tests and all distributions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the point of this file? Seem it will never actually be used, as most external application tests vary so much, it does not have value. Drop it. It can be added in a later PR when and if it becomes needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. But before I do, please check: 5f88915#diff-3e2f7b162bce7417355d4fe0f18e6366b69e6abe5e73356f863274d3a4f9c04bR2 Those generic_packages="git wget unzip tar curl" are used in 99% of tests (see the rest of 5f88915 and search for -ubuntu_packages or -ubi_packages). I think such base really should be added. (note, this is not disagreeing with removal, just asking for brainstroming. I will remove it solemnly on this comment).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smlambert Even if practically unused, I would liek to keep this file. Is is extremly helpful when new distribution is tried. It simply allows you to declare its specific dependencies on one place, and then you can easily pinpoint it. And if this file remains unused for future, then they can be tuned up for individual suites alter.

generic_packages=""
# those packages are included in all tests and all ubuntu
ubuntu_packages=""
# those packages are included in all tests and all Red Hat distributions
(fedora|ubi|rhel|centos)_packages=""
68 changes: 58 additions & 10 deletions external/common_functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#

source $(dirname "$0")/provider.sh

# Supported JVMs
supported_jvms="hotspot openj9"

Expand All @@ -28,6 +31,11 @@ supported_builds="full"
# Supported tests
supported_tests="external_custom camel criu-functional criu-portable-checkpoint criu-portable-restore criu-ubi-portable-checkpoint criu-ubi-portable-restore derby elasticsearch jacoco jenkins functional-test kafka lucene-solr openliberty-mp-tck payara-mp-tck quarkus quarkus_quickstarts scala system-test tck-ubi-test tomcat tomee wildfly wycheproof netty spring zookeeper"

# Set a valid version
function set_version() {
version=$1
}

function check_os() {
os=$1

Expand Down Expand Up @@ -161,11 +169,34 @@ function set_base_docker_registry_dir() {

# Reading properties of test.properties file
function getProperty() {
PROP_KEY=$1
PROP_VALUE=`cat $PROPERTY_FILE | grep "$PROP_KEY" | cut -d'=' -f 2-`
local PROP_KEY="${1}"
local FILE=${2:-""}
if [ -z "${FILE}" ] ; then
FILE="${PROPERTY_FILE}"
fi
local PROP_VALUE=`cat "${FILE}" | grep -v "^#" | grep "^${PROP_KEY}=" | cut -d'=' -f 2-`
echo `sed -e 's/^"//' -e 's/"$//' <<<"$PROP_VALUE"`
}

# Getting matching keys for given OS
function getMatchingPackagesKeys() {
local FILE=${1}
local OS=$(getImageOs)
local TAG=$(getImageTag)
local KEYS=""
while read line; do
if echo "$line" | grep -q "^#" ; then
continue
fi
local keyCandidate=$(echo $line | cut -d'=' -f 1)
local strippedKeyCandidate=$(echo $keyCandidate | sed "s/_packages//")
if echo "${OS}:${TAG}" | grep -Eqe ".*${strippedKeyCandidate}.*" ; then
KEYS="$KEYS $keyCandidate"
fi
done < "${FILE}"
echo ${KEYS}
}

# Used for external_custom tests
function set_external_custom_test_info(){
test=$1
Expand All @@ -175,17 +206,20 @@ function set_external_custom_test_info(){
test_results="testResults"
tag_version="${EXTERNAL_REPO_BRANCH}"
environment_variable="MODE=java"
ubuntu_packages="git"
maven_version="3.8.5"
packages="git"
}

# Set the valid OSes for the current architectures.
function set_test_info() {
test=$1
local test=$1
check_external_custom_test=$2
cd ../
path_to_file=$(pwd)
local path_to_file=$(cd $(dirname "$0") && pwd)
echo ${path_to_file}
# global settings will be amend to local ones
local GLOBAL_PROPERTY_FILE=${path_to_file}/common.properties
local global_generic_packages=$(getProperty "generic_packages" "${GLOBAL_PROPERTY_FILE}")

PROPERTY_FILE=${path_to_file}/${test}/test.properties
github_url=$(getProperty "github_url")
test_options=$(getProperty "test_options")
Expand All @@ -204,14 +238,28 @@ function set_test_info() {
maven_version=$(getProperty "maven_version")
environment_variable=$(getProperty "environment_variable")
localPropertyFile=$(getProperty "localPropertyFile")
ubuntu_packages=$(getProperty "ubuntu_packages")
ubi_packages=$(getProperty "ubi_packages")
local local_generic_packages=$(getProperty "generic_packages")

packages="$global_generic_packages $local_generic_packages"
local gobalMatchingKeys=$(getMatchingPackagesKeys "${GLOBAL_PROPERTY_FILE}")
for key in ${gobalMatchingKeys} ; do
packages="$packages $(getProperty $key" "${GLOBAL_PROPERTY_FILE}\")"
done
local localMatchingKeys=$(getMatchingPackagesKeys "${PROPERTY_FILE}")
for key in ${localMatchingKeys} ; do
packages="$packages $(getProperty "$key" "${PROPERTY_FILE}")"
done
}

function cleanup_images() {
local container_rm=$(getExternalImageCommand)
if [ "${EXTERNAL_AQA_CONTAINER_CLEAN}" == "false" ] ; then
echo "to debug, put '-i --entrypoint /bin/bash' before container name"
container_rm="echo to clean, run manually: $container_rm"
fi
# Delete any old containers that have exited.
docker rm $(docker ps -a | grep "Exited" | awk '{ print $1 }') 2>/dev/null
${container_rm} rm $(docker ps -a | grep "Exited" | awk '{ print $1 }') 2>/dev/null

# Delete any old images for our target_repo on localhost.
docker rmi -f $(docker images | grep -e "adoptopenjdk" | awk '{ print $3 }' | sort | uniq) 2>/dev/null
${container_rm} rmi -f $(docker images | grep -e "adoptopenjdk" | awk '{ print $3 }' | sort | uniq) 2>/dev/null
}
5 changes: 4 additions & 1 deletion external/criu-functional/test.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@ github_url="https://github.com/adoptium/aqa-tests.git"
test_results="testResults"
gradle_version="5.1"
environment_variable="MODE=java CC=gcc-7 CXX=g++-7"
ubuntu_packages="ant-contrib build-essential git asciidoc"
generic_packages="git asciidoc"
ubuntu_packages="ant-contrib build-essential"
fedora_packages="ant-contrib"
(fedora|ubi|rhel|centos)_packages=make automake gcc gcc-c++ kernel-devel
criu_version="latest"
5 changes: 4 additions & 1 deletion external/criu-portable-checkpoint/test.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@ github_url="https://github.com/adoptium/aqa-tests.git"
test_results="testResults"
gradle_version="5.1"
environment_variable="MODE=java CC=gcc-7 CXX=g++-7"
ubuntu_packages="ant-contrib build-essential git asciidoc"
generic_packages="git asciidoc"
ubuntu_packages="ant-contrib build-essential"
fedora_packages="ant-contrib"
(fedora|ubi|rhel|centos)_packages=make automake gcc gcc-c++ kernel-devel
criu_version="latest"
2 changes: 1 addition & 1 deletion external/criu-ubi-portable-checkpoint/test.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ github_url="https://github.com/adoptium/aqa-tests.git"
test_results="testResults"
gradle_version="5.1"
environment_variable="MODE=java"
ubi_packages="git wget perl"
generic_packages="git wget perl make"
3 changes: 2 additions & 1 deletion external/derby/test.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
github_url="https://github.com/apache/derby.git"
localPropertyFile="local.properties"
tag_version="trunk"
ubuntu_packages="ant-optional git wget tar"
ubuntu_packages="ant-optional"
generic_packages="git wget tar"
60 changes: 43 additions & 17 deletions external/dockerfile_functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,34 +84,50 @@ print_image_args() {
local build=$7
local base_docker_registry_dir="$8"

image_name="docker.io/library/eclipse-temurin"
tag=""
if [[ "${package}" == "jre" ]]; then
tag="${version}-jre"
else
tag="${version}-jdk"
fi
image_name="$(getTemurinImageName)"
tag="$(getTemurinImageTag "${version}" "${package}")"
if [[ "${vm}" == "openj9" ]]; then
if [[ "${os}" == "ubuntu" ]]; then
image_name="docker.io/ibm-semeru-runtimes"
tag=open-${tag}
image_name="$(getOpenJ9ImageName)"
tag="$(getOpenJ9ImageTag "${version}" "${package}")"
elif [[ "${os}" == *"ubi"* && "${test}" != *"criu"* ]]; then
if isExternalImageEnabled ; then
echo "openj9 ubi and custom EXTERNAL_AQA_IMAGE are not compatible"
exit 1
fi
image_name="registry.access.redhat.com/$base_docker_registry_dir"
tag="latest"
EXTERNAL_AQA_IMAGE="${image_name}:${tag}"
else
if isExternalImageEnabled ; then
echo "openj9 ubi and custom EXTERNAL_AQA_IMAGE are not compatible"
exit 1
fi
# os is ubi, and test is criu
# temporarily all ubi based testing use internal base image
image_name="$DOCKER_REGISTRY_URL/$base_docker_registry_dir"
tag="latest"
EXTERNAL_AQA_IMAGE="${image_name}:${tag}"
Copy link
Contributor

@sophia-guo sophia-guo Sep 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If EXTERNAL_AQA_IMAGE needs to be set? If yes, should move to line 114 for both temurin and openj9 ? Looks like this is not needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unhappy workaround for a corner case I was unable to manage. The only correct sollution to this is described in middle of initial description of issue #5555

The initial change should affect at least the second. To affect the build_all.sh on top of that, should be refactoring, which would remove all the for test in ${supported_tests} do for vm in ${supported_jvms} do for os in ${supported_os} do for package in ${supported_packages} do for build in ${supported_builds} do build_image.sh ${test} ${version} ${vm} ${os} ${package} ${build} to actually do all the looping through ${supported_jvms}, ${supported_os}, ${supported_packages},  ${supported_builds} first, to prepare set of (fully) qualified container IDs and then loop through them, via shortened for test in ${supported_tests} do build_image.sh ${test} ${image} I would probably like to addres it in different issue and different purpose as usage of build-all.sh is unclear to me.

This triple-if is changing to much to be served by the methods in provider.sh (without adding similar triple if there).
If the EXTERNAL_AQA_IMAGE is set in this combination, the exit is called. If not (and that is the only expected case as you need to set three parameters out of any defaults to actually overwrite them by this last-of-three ifs the setup of EXTERNAL_AQA_IMAGE is ensuring the values detected later (which are no longer consistent with the set ones because of those ifs) are correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To construct the EXTERNAL_AQA_IMAGE artificially and globaly, is quite a good idea, and I was playing with it a lot. The reason why I have not included it, are non deterministic OSes. Ubuntu was hardcoded, to be there for jdk-xy-temurin/openj9, because there is no record of ubuntu in name. From my limited knowledge, this is the only case where it is actually missing. Eg redhat java images still have ubi in name, and so on.

If there will be more usage of EXTERNAL_AQA_IMAGE then current baseurl, path, name, os variables, then I guess such a crossroad table would be necessary, but for now I decided to not implement it.

fi
fi
image="${image_name}:${tag}"

echo -e "ARG IMAGE=${image}" \
if isExternalImageEnabled ; then
os=$(getImageOs)
echo -e \
"ARG IMAGE=${image}" \
"\nARG OS=${os}" \
"\nARG IMAGE_VERSION=nightly" \
"\nARG TAG=${tag}" \
"\nFROM \$IMAGE\n" >> ${file}
else
echo -e \
"ARG IMAGE=${image}" \
"\nARG OS=${os}" \
"\nARG IMAGE_VERSION=nightly" \
"\nARG TAG=${tag}" \
"\nFROM \$IMAGE\n" >> ${file}
fi
}

print_test_tag_arg() {
Expand All @@ -133,8 +149,6 @@ print_result_comment_arg() {
# Select the ubuntu OS packages
print_ubuntu_pkg() {
local file=$1
local packages=$2

echo -e "RUN apt-get update \\" \
"\n\t&& apt-get install -qq -y --no-install-recommends software-properties-common \\" \
"\n\t&& apt-get install -qq -y --no-install-recommends gnupg \\" \
Expand All @@ -148,8 +162,6 @@ print_ubuntu_pkg() {
# Select the ubuntu OS packages
print_ubi_pkg() {
local file=$1
local packages=$2

echo -e "RUN dnf install -y ${packages} \\" \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable packages was removed, but still used here. This might be the reason why RUN dnf install -y && dnf clean all cannot catch any packages.
If some core functions in this PR are needed in urgent, as Sophia suggested, we can extract this PR to only address the core functions, then work on non-core functions, e.g. generice_packages in other PRs, combining them all in this PR makes it easy to break current build. Thanks.

"\n\t&& dnf clean all " >> ${file}
echo -e "\nENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8'" >> ${file}
Expand Down Expand Up @@ -212,6 +224,9 @@ print_ant_install() {
local file=$1
local ant_version=$2
local os=$3
if isExternalImageEnabled ; then
os=$(getImageOs)
karianna marked this conversation as resolved.
Show resolved Hide resolved
fi

echo -e "ARG ANT_VERSION=${ant_version}" \
"\nENV ANT_VERSION=\$ANT_VERSION" \
Expand Down Expand Up @@ -501,6 +516,9 @@ print_testInfo_env() {
local OS=$3
local version=$4
local vm=$5
if isExternalImageEnabled ; then
OS=$(getImageOs)
fi
echo -e "ENV APPLICATION_NAME=${test}" \
"\nENV APPLICATION_TAG=${test_tag}" \
"\nENV OS_TAG=${OS}" \
Expand Down Expand Up @@ -593,7 +611,6 @@ generate_dockerfile() {
base_docker_registry_dir="$9"
check_external_custom_test=$10


if [[ "$check_external_custom_test" == "1" ]]; then
tag_version=${EXTERNAL_REPO_BRANCH}
fi
Expand All @@ -603,7 +620,7 @@ generate_dockerfile() {
else
set_test_info ${test} ${check_external_custom_test}
fi
packages=$(echo ${os}_packages | sed 's/-/_/')

jhome="/opt/java/openjdk"

mkdir -p `dirname ${file}` 2>/dev/null
Expand All @@ -612,9 +629,18 @@ generate_dockerfile() {
print_legal ${file};
print_adopt_test ${file} ${test};
print_image_args ${file} ${test} ${os} ${version} ${vm} ${package} ${build} "${base_docker_registry_dir}";
# print_image_args is setting up hackisch EXTERNAL_AQA_IMAGE for some corner cases
osDeducted=$(getImageOs)
print_result_comment_arg ${file};
print_test_tag_arg ${file} ${test} ${tag_version};
print_${os}_pkg ${file} "${!packages}";
if echo ${osDeducted} | grep -i -e ubuntu -e debian ; then
print_ubuntu_pkg ${file}
elif echo ${osDeducted} | grep -i -e ubi -e fedora -e rhel -e centos ; then
print_ubi_pkg ${file}
else
echo "unknown os: ${osDeducted} (${os})"
exit 1
fi

if [[ ! -z ${ant_version} ]]; then
print_ant_install ${file} ${ant_version} ${os};
Expand Down
2 changes: 1 addition & 1 deletion external/elasticsearch/test.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github_url="https://github.com/elastic/elasticsearch.git"
tag_version="v8.1.2"
test_results="testResults"
ubuntu_packages="git wget unzip"
generic_packages="git wget unzip"

27 changes: 27 additions & 0 deletions external/external.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,33 @@ set -e

source $(dirname "$0")/provider.sh

if [ -z "${EXTRA_DOCKER_ARGS}" ] ; then
echo \
" # ================= WARNING ================= WARNING ================= WARNING ================= #
# EXTRA_DOCKER_ARGS are not set. You will be testing java which is already in container and not TEST_JDK_HOME
# TEST_JDK_HOME is set to $TEST_JDK_HOME but will not be used. See test_base_functions.sh for order of search
# You should set your's TEST_JDK_HOME to mount to /opt/java/openjdk, eg: #
# export EXTRA_DOCKER_ARGS=\"-v \$TEST_JDK_HOME:/opt/java/openjdk\" #
# ================= WARNING ================= WARNING ================= WARNING ================= #"
else
echo \
" # =================== Info =================== Info =================== Info =================== #
# EXTRA_DOCKER_ARGS set as \"$EXTRA_DOCKER_ARGS\" #
# =================== Info =================== Info =================== Info =================== #"
if echo "${EXTRA_DOCKER_ARGS}" | grep -q "$TEST_JDK_HOME" ; then
echo \
" # =================== Info =================== Info =================== Info =================== #
# TEST_JDK_HOME of $TEST_JDK_HOME is used in EXTRA_DOCKER_ARGS #
# =================== Info =================== Info =================== Info =================== #"
else
echo \
" # ================= WARNING ================= WARNING ================= WARNING ================= #
# TEST_JDK_HOME of $TEST_JDK_HOME is NOT used in EXTRA_DOCKER_ARGS #
# ================= WARNING ================= WARNING ================= WARNING ================= #"
fi
fi


tag=nightly
docker_os=ubuntu
build_type=full
Expand Down
Loading