Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
56 changes: 38 additions & 18 deletions test/e2e/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,31 @@ data "lxd_info" "cluster" {
remote = var.remote
}

# Project
# Check whether the project already exists
data "external" "project_exists" {
program = ["bash", "-c", "lxc project list \"${var.remote}:\" -f csv | awk -F, '{gsub(/ \\(current\\)$/, \"\", $1); print $1}' | grep -qxF \"${var.project}\" && echo '{\"exists\": \"true\"}' || echo '{\"exists\": \"false\"}'"]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Used AI for this ugly command FYI.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mh, if there is no easy way to achieve this, I would say let's have an additional TF var as you have proposed right from the start.
Doing this seems to be not the right way.

@roosterfish roosterfish Aug 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@MusicDin do you know if there is a way to check for an existing project/profile and create if it doesn't yet exist (in terraform)?

}

# Check whether the profile already exists
data "external" "profile_exists" {
program = ["bash", "-c", "lxc profile list --project \"${var.project}\" \"${var.remote}:\" -f csv 2>/dev/null | awk -F, '{gsub(/ \\(current\\)$/, \"\", $1); print $1}' | grep -qxF \"${var.profile}\" && echo '{\"exists\": \"true\"}' || echo '{\"exists\": \"false\"}'"]
}

# Project - created only when it does not already exist
resource "lxd_project" "e2e" {
name = "e2e-testing"
count = data.external.project_exists.result["exists"] == "true" ? 0 : 1
name = var.project
remote = var.remote
}

# Profile
# Profile - created only when it does not already exist
resource "lxd_profile" "e2e" {
name = "e2e-testing"
project = lxd_project.e2e.name
remote = lxd_project.e2e.remote
count = data.external.profile_exists.result["exists"] == "true" ? 0 : 1
name = var.profile
project = var.project
remote = var.remote

depends_on = [lxd_project.e2e]

# Configuration
config = {
Expand Down Expand Up @@ -44,20 +58,24 @@ resource "lxd_profile" "e2e" {
# Images
resource "lxd_cached_image" "ctn" {
count = var.containers_per_host > 0 ? 1 : 0
project = lxd_project.e2e.name
remote = lxd_project.e2e.remote
source_remote = "ubuntu-minimal-daily"
project = coalesce(var.image_project, var.project)
remote = var.remote
source_remote = var.image_remote
source_image = "24.04"
type = "container"
copy_aliases = var.copy_image_aliases
depends_on = [lxd_project.e2e]
}

resource "lxd_cached_image" "vm" {
count = var.vms_per_host > 0 ? 1 : 0
project = lxd_project.e2e.name
remote = lxd_project.e2e.remote
source_remote = "ubuntu-minimal-daily"
project = coalesce(var.image_project, var.project)
remote = var.remote
source_remote = var.image_remote
source_image = "24.04"
type = "virtual-machine"
copy_aliases = var.copy_image_aliases
depends_on = [lxd_project.e2e]
}

# Containers
Expand All @@ -69,11 +87,12 @@ resource "lxd_instance" "e2e-ctn" {
name = each.key
target = each.value
type = "container"
remote = lxd_project.e2e.remote
project = lxd_project.e2e.name
profiles = [lxd_profile.e2e.name]
remote = var.remote
project = var.project
profiles = [var.profile]
image = lxd_cached_image.ctn[0].fingerprint
wait_for_network = true
depends_on = [lxd_project.e2e, lxd_profile.e2e]
Comment thread
roosterfish marked this conversation as resolved.
}

# VMs
Expand All @@ -85,12 +104,13 @@ resource "lxd_instance" "e2e-vm" {
name = each.key
target = each.value
type = "virtual-machine"
remote = lxd_project.e2e.remote
project = lxd_project.e2e.name
profiles = [lxd_profile.e2e.name]
remote = var.remote
project = var.project
profiles = [var.profile]
image = lxd_cached_image.vm[0].fingerprint
wait_for_network = true
allow_restart = true
depends_on = [lxd_project.e2e, lxd_profile.e2e]

config = {
"migration.stateful" = "true"
Expand Down
21 changes: 11 additions & 10 deletions test/e2e/run
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ done
set -- "${ARGS[@]}"

REMOTE="${REMOTE:-${1:-""}}"
PROJECT="${PROJECT:-e2e-testing}"
DESTROY="${DESTROY:-prompt}"
EVACUATION_COUNTS="${EVACUATION_COUNTS:-1}"

Expand Down Expand Up @@ -173,15 +174,15 @@ apply() {
# 2025-04-01T14:38:07.671+0000 7f8284b55640 -1 librbd::image::OpenRequest: failed to find snapshot readonly
# 2025-04-01T14:38:07.671+0000 7f826bfff640 -1 librbd::image::CloneRequest: 0x55b8434ed7c0 handle_open_parent: failed to open parent image: (2) No such file or directory
# rbd: clone error: (2) No such file or directory)
retry terraform apply -auto-approve -var "remote=${REMOTE}"
retry terraform apply -auto-approve -var "remote=${REMOTE}" -var "project=${PROJECT}"
}

destroy() {
echo "terraform destroy"
if [ "${DESTROY}" = "prompt" ]; then
terraform destroy -var "remote=${REMOTE}"
terraform destroy -var "remote=${REMOTE}" -var "project=${PROJECT}"
elif [ "${DESTROY}" = "yes" ]; then
terraform destroy -auto-approve -var "remote=${REMOTE}"
terraform destroy -auto-approve -var "remote=${REMOTE}" -var "project=${PROJECT}"
elif [ "${DESTROY}" = "no" ] ; then
echo "Skipping destroy"
else
Expand All @@ -199,20 +200,20 @@ setup() {
}

instanceList() {
lxc list --project e2e-testing -f csv -c n "${REMOTE}:"
lxc list --project "${PROJECT}" -f csv -c n "${REMOTE}:"
}

instanceListByMember() {
local member="${1}"
lxc list --project e2e-testing -f csv -c n "${REMOTE}:" location="${member}"
lxc list --project "${PROJECT}" -f csv -c n "${REMOTE}:" location="${member}"
}

clusterMembers() {
local state="${1}"
[ "${state}" = "ALL" ] && state=".*"
# XXX: cannot use the column number for the member state as if multiple
# roles are provided, the column number will change
lxc cluster list --project e2e-testing -f csv "${REMOTE}:" | awk -F, "/,${state},/ {print \$1}"
lxc cluster list --project "${PROJECT}" -f csv "${REMOTE}:" | awk -F, "/,${state},/ {print \$1}"
}

randomPick() {
Expand All @@ -225,7 +226,7 @@ randomPick() {

getIPv4() {
local instance="${1}"
lxc list --project e2e-testing -f csv -c 4 "${REMOTE}:" name="${instance}" | cut -d" " -f1
lxc list --project "${PROJECT}" -f csv -c 4 "${REMOTE}:" name="${instance}" | cut -d" " -f1
}

checkConnectivity() {
Expand All @@ -242,7 +243,7 @@ checkConnectivity() {
dstIPv4="$(getIPv4 "${dst}")"

echo -n " ● ${src} => ${dst} (${dstIPv4}):"
lxc exec --project e2e-testing "${REMOTE}:${src}" -- bash -c "grep -qm1 ^SSH- < /dev/tcp/${dstIPv4}/22"
lxc exec --project "${PROJECT}" "${REMOTE}:${src}" -- bash -c "grep -qm1 ^SSH- < /dev/tcp/${dstIPv4}/22"
}

# Use the retry function in case the target isn't yet reachable.
Expand Down Expand Up @@ -275,7 +276,7 @@ evacuation() {

for member in $(clusterMembers ONLINE); do
echo "Evacuating ${member}"
lxc cluster evacuate --project e2e-testing --force "${REMOTE}:${member}"
lxc cluster evacuate --project "${PROJECT}" --force "${REMOTE}:${member}"

echo "Waiting for ${member} to be rebooted"
if [ -x "./reboot" ]; then
Expand All @@ -288,7 +289,7 @@ evacuation() {
read -rp "Press enter to continue with the evacuation test"
fi

lxc cluster restore --project e2e-testing --force "${REMOTE}:${member}"
lxc cluster restore --project "${PROJECT}" --force "${REMOTE}:${member}"

echo -n "Allow time for stopped instances on ${member} to start back again and have an IPv4 address"
for instance in $(instanceListByMember "${member}"); do
Expand Down
23 changes: 23 additions & 0 deletions test/e2e/terraform.tfvars.custom-image-server.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# LXD remote corresponding to the MicroCloud deployment to test.
Comment thread
ggouzi marked this conversation as resolved.
remote = "mc"

# LXD remote to fetch source images from
image_remote = "pcr-d"

# LXD project to cache source images into. Only needed when `project` has features.images=false
# (here, canonical-testing shares images with default).
image_project = "default"

# Whether to copy the source image's aliases to the cached image copy
# Set to true if image_remote's images carry aliases
copy_image_aliases = false

# LXD project/profile to use. By default, a project and profile named e2e-testing
# are created. Can be configured to reuse an existing project/profile by providing
# their names here; the project and profile will be created only if they do not already exist.
project = "canonical-testing"
profile = "canonical-testing-profile"

# Number of containers/VMs to create per cluster member.
# containers_per_host = 9
# vms_per_host = 3
30 changes: 30 additions & 0 deletions test/e2e/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,36 @@ variable "remote" {
}
}

variable "image_remote" {
description = "LXD remote to fetch images from"
type = string
default = "ubuntu-minimal-daily"
}

variable "image_project" {
description = "LXD project to cache source images into. Defaults to `project`. Set to \"default\" when `project` has features.images=false and shares images with another project instead"
type = string
default = null
}

variable "copy_image_aliases" {
description = "Whether to copy the source image's aliases to the cached image copy. Set to true if image_remote's images carry aliases"
type = bool
default = false
}

variable "project" {
description = "LXD project to use for e2e testing resources"
type = string
default = "e2e-testing"
}

variable "profile" {
description = "LXD profile to use for e2e testing instances"
type = string
default = "e2e-testing"
}

variable "containers_per_host" {
description = "Number of containers per host"
type = number
Expand Down
Loading